<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/blog/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />




  
  
  
  

  
    
    
  

  
    
      
    

    
  

  

  
    
      
    

    
  

  
    
      
    

    
  

  
    
    
    <link href="//fonts.googleapis.com/css?family=Monda:300,300italic,400,400italic,700,700italic|Roboto Slab:300,300italic,400,400italic,700,700italic|PT Mono:300,300italic,400,400italic,700,700italic|PT Mono:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/blog/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/blog/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/blog/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/blog/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/blog/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/blog/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT" />










<meta name="description" content="JUST DO IT.">
<meta property="og:type" content="website">
<meta property="og:title" content="Srtian&#39;Blog">
<meta property="og:url" content="http://srtian96.gitee.io/blog/page/6/index.html">
<meta property="og:site_name" content="Srtian&#39;Blog">
<meta property="og:description" content="JUST DO IT.">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Srtian&#39;Blog">
<meta name="twitter:description" content="JUST DO IT.">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/blog/',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://srtian96.gitee.io/blog/page/6/"/>





  <title>Srtian'Blog</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left 
  page-home">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/blog/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Srtian'Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">It's better to burn out than to fade away</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/blog/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/blog/about/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />
            
            关于
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/blog/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/blog/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/blog/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br />
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off"
             placeholder="搜索..." spellcheck="false"
             type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
  <section id="posts" class="posts-expand">
    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/Node.js核心入门（一）/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/Node.js核心入门（一）/" itemprop="url">Node.js核心入门（一）</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/Node-js/" itemprop="url" rel="index">
                    <span itemprop="name">Node.js</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  3,142
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  13
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h4 id="前言："><a href="#前言：" class="headerlink" title="前言："></a>前言：</h4><p>因为以前学习Node.js并没有真正意义上的去学习它，而是粗略的学习了npm的常用命令和Node.js一些模块化的语法，因此昨天花了一天的时间看了《Node.js开发指南》一书。通过这本书倒是让我对Node.js的认识更为全面，但由于这本书出版时间过早，有些API已经发生了变化或已经被废弃，而对于学习Node.js来说，核心部分又是最为重要的一环，因此配合官方文档对这本书的第四章-Node.js核心进行了总结与梳理，如有疏漏与错误，请指正。</p>
<h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>核心模块是Node.js的心脏，主要是有一些精简高效的库组成（这方面和Python有很大的相似之处），为Node.js提供了基础的API。主要内容包括：</p>
<p>Node.js核心入门（一）</p>
<ul>
<li>全局对象</li>
<li>常用工具</li>
<li>事件机制</li>
</ul>
<p>Node.js核心入门（二）</p>
<ul>
<li>文件系统访问</li>
<li>HTTP服务器与客户端<h2 id="全局对象"><a href="#全局对象" class="headerlink" title="全局对象"></a>全局对象</h2>全局对象我想学过JavaScript的都知道在浏览器是window，在程序的任何地方都可以访问到全局对象，而在Node.js中，这个全局对象换成了global，所有的全局变量（除了global本身）都是global对象的属性。而我们在Node.js中能够直接访问的对象通常都是global的属性，如：console，process等。<h3 id="全局对象与全局变量"><a href="#全局对象与全局变量" class="headerlink" title="全局对象与全局变量"></a>全局对象与全局变量</h3>global最根本的作用就是作为全局变量的宿主。按照ECMAScript规范，满足以下条件的变量即为全局变量：</li>
<li>在最外层定义的变量（在Node.js中不存在，因为Node.js的代码在模块中执行，不存在在最外层定义变量）</li>
<li>全局对象的属性</li>
<li>隐式定义的变量（即未定义而直接进行赋值的变量）</li>
</ul>
<p>当我们定义一个全局变量的时候，这个全局变量会自动成为全局变量的属性。</p>
<h3 id="process"><a href="#process" class="headerlink" title="process"></a>process</h3><p>process 对象是一个全局变量，它提供当前 Node.js 进程的相关信息，以及控制当前 Node.js 进程。通常我们在写本地命令行程序的时候，少不了和它打交道。下面是它的最常用的成员方法：</p>
<h4 id="1-process-argv"><a href="#1-process-argv" class="headerlink" title="1.process.argv"></a>1.process.argv</h4><p>process.argv 属性返回一个数组，这个数组包含了启动Node.js进程时的命令行参数。第一个元素为process.execPath，第二个元素为当前执行的JavaScript文件路径，剩余的元素为其他命令行参数。</p>
<p>例如存储一个名为argv.js的文件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// print process.argv</span></span><br><span class="line">process.argv.forEach(<span class="function">(<span class="params">val, index</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`<span class="subst">$&#123;index&#125;</span>: <span class="subst">$&#123;val&#125;</span>`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>则命令行运行时这样的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ node process-args.js one two=three four</span><br><span class="line"></span><br><span class="line"><span class="number">0</span>: <span class="regexp">/usr/</span>local/bin/node</span><br><span class="line"><span class="number">1</span>: <span class="regexp">/Users/m</span>jr/work/node/process-args.js</span><br><span class="line"><span class="number">2</span>: one</span><br><span class="line"><span class="number">3</span>: two=three</span><br><span class="line"><span class="number">4</span>: four</span><br></pre></td></tr></table></figure>
<h4 id="2-process-stdout"><a href="#2-process-stdout" class="headerlink" title="2.process.stdout"></a>2.process.stdout</h4><p>process.stdout是标准输出流，通常我们使用的console.log()输出打印字符，而process.stdout.write()函数提供了更为底层的接口。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">process.stdout.write(<span class="string">'请输入num1的值：'</span>);</span><br></pre></td></tr></table></figure></p>
<h4 id="3-process-stdin"><a href="#3-process-stdin" class="headerlink" title="3.process.stdin"></a>3.process.stdin</h4><p>process.stdin是标准输入流，初始时它是暂停的，要想从标准输入读取数据，我们必须恢复流，并手动编写流的事件响应函数。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*1:声明变量*/</span></span><br><span class="line"><span class="keyword">var</span> num1, num2;</span><br><span class="line"><span class="comment">/*2：向屏幕输出，提示信息，要求输入num1*/</span></span><br><span class="line">process.stdout.write(<span class="string">'请输入num1的值：'</span>);</span><br><span class="line"><span class="comment">/*3：监听用户的输入*/</span></span><br><span class="line">process.stdin.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">chunk</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!num1) &#123;</span><br><span class="line">        num1 = <span class="built_in">Number</span>(chunk);</span><br><span class="line">        <span class="comment">/*4：向屏幕输出，提示信息，要求输入num2*/</span></span><br><span class="line">        process.stdout.write(<span class="string">'请输入num2的值'</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        num2 = <span class="built_in">Number</span>(chunk);</span><br><span class="line">        process.stdout.write(<span class="string">'结果是：'</span> + (num1 + num2));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p>
<h4 id="4-process-nextTick-callback-…args"><a href="#4-process-nextTick-callback-…args" class="headerlink" title="4.process.nextTick(callback[, …args])"></a>4.process.nextTick(callback[, …args])</h4><p>…args <any> 调用 callback时传递给它的额外参数<br>process.nextTick()方法将 callback 添加到”next tick 队列”。 一旦当前事件轮询队列的任务全部完成，在next tick队列中的所有callbacks会被依次调用。<br>这种方式不是setTimeout(fn, 0)的别名。它更加有效率，因此别用setTimeout去代替process.nextTick。事件轮询随后的ticks 调用，会在任何I/O事件（包括定时器）之前运行。</any></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'start'</span>);</span><br><span class="line">process.nextTick(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'nextTick callback'</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'scheduled'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// start</span></span><br><span class="line"><span class="comment">// scheduled</span></span><br><span class="line"><span class="comment">// nextTick callback</span></span><br></pre></td></tr></table></figure>
<h3 id="console"><a href="#console" class="headerlink" title="console"></a>console</h3><p>console 模块提供了一个简单的调试控制台，类似于 Web 浏览器提供的 JavaScript 控制台。该模块导出了两个特定的组件：</p>
<ul>
<li>一个 Console 类，包含 console.log() 、 console.error() 和 console.warn() 等方法，可以被用于写入到任何 Node.js 流。</li>
<li>一个全局的 console 实例，可被用于写入到 process.stdout 和 process.stderr。 全局的 console 使用时无需调用 require(‘console’)。(注意：全局的 console 对象的方法既不总是同步的（如浏览器中类似的 API），也不总是异步的（如其他 Node.js 流)。</li>
</ul>
<p>比如全局下的常见的console实例：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'hello，world'</span>);</span><br><span class="line"><span class="comment">// hello，world</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'hello%s'</span>, <span class="string">'world'</span>);</span><br><span class="line"><span class="comment">// helloworld</span></span><br><span class="line"><span class="built_in">console</span>.error(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'错误信息'</span>));</span><br><span class="line"><span class="comment">//  Error: 错误信息</span></span><br><span class="line"><span class="keyword">const</span> name = <span class="string">'描述'</span>;</span><br><span class="line"><span class="built_in">console</span>.warn(<span class="string">`警告<span class="subst">$&#123;name&#125;</span>`</span>);</span><br><span class="line"><span class="comment">// 警告描述</span></span><br><span class="line"><span class="built_in">console</span>.trace() <span class="comment">// 向标准错误流输出当前的调用栈</span></span><br><span class="line"><span class="string">``</span><span class="string">`JavaScript</span></span><br><span class="line"><span class="string">常见的Console类：</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">`</span><span class="string">``</span>JavaScript</span><br><span class="line"><span class="keyword">const</span> out = getStreamSomehow();</span><br><span class="line"><span class="keyword">const</span> err = getStreamSomehow();</span><br><span class="line"><span class="keyword">const</span> myConsole = <span class="keyword">new</span> <span class="built_in">console</span>.Console(out, err);</span><br><span class="line"></span><br><span class="line">myConsole.log(<span class="string">'hello，world'</span>);</span><br><span class="line"><span class="comment">// hello，world</span></span><br><span class="line">myConsole.log(<span class="string">'hello%s'</span>, <span class="string">'world'</span>);</span><br><span class="line"><span class="comment">// helloworld</span></span><br><span class="line">myConsole.error(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'错误信息'</span>));</span><br><span class="line"><span class="comment">// Error: 错误信息</span></span><br><span class="line"><span class="keyword">const</span> name = <span class="string">'描述'</span>;</span><br><span class="line">myConsole.warn(<span class="string">`警告<span class="subst">$&#123;name&#125;</span>`</span>);</span><br><span class="line"><span class="comment">//警告描述</span></span><br></pre></td></tr></table></figure>
<h2 id="常用工具-util"><a href="#常用工具-util" class="headerlink" title="常用工具  util"></a>常用工具  util</h2><p>util 模块主要用于支持 Node.js 内部 API 的需求。 大部分实用工具也可用于应用程序与模块开发者，用于弥补核心JavaScript的功能的不足。它的可以这样调用：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">'util'</span>);</span><br></pre></td></tr></table></figure>
<h3 id="1-util-inspect-object-options"><a href="#1-util-inspect-object-options" class="headerlink" title="1.util.inspect(object[, options])"></a>1.util.inspect(object[, options])</h3><p>util.inspect() 方法返回 object 的字符串表示，主要用于调试和错误输出。 附加的 options 可用于改变格式化字符串的某些方面。它至少接受一个参数objet，即要转换的参数，而option则是可选的，可选内容如下：</p>
<ul>
<li>showHidden <boolean> 如果为 true，则 object 的不可枚举的符号与属性也会被包括在格式化后的结果中。 默认为 false。</boolean></li>
<li>depth <number> 指定格式化 object 时递归的次数。 这对查看大型复杂对象很有用。 默认为 2。 若要无限地递归则传入 null。</number></li>
<li>colors <boolean> 如果为 true，则输出样式使用 ANSI 颜色代码。 默认为 false，可自定义。</boolean></li>
<li>customInspect <boolean> 如果为 false，则 object 上自定义的 inspect(depth, opts) 函数不会被调用。 默认为 true。</boolean></li>
<li>showProxy <boolean> 如果为 true，则 Proxy 对象的对象和函数会展示它们的 target 和 handler 对象。 默认为 false。</boolean></li>
<li>maxArrayLength <number> 指定格式化时数组和 TypedArray 元素能包含的最大数量。 默认为 100。 设为 null 则显式全部数组元素。 设为 0 或负数则不显式数组元素。</number></li>
<li>breakLength <number> 一个对象的键被拆分成多行的长度。 设为 Infinity 则格式化一个对象为单行。 默认为 60。</number></li>
</ul>
<p>例如，查看 util 对象的所有属性：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">'util'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(util.inspect(util, &#123; <span class="attr">showHidden</span>: <span class="literal">true</span>, <span class="attr">depth</span>: <span class="literal">null</span> &#125;));</span><br></pre></td></tr></table></figure>
<h3 id="2-util-callbackify-original"><a href="#2-util-callbackify-original" class="headerlink" title="2.util.callbackify(original)"></a>2.util.callbackify(original)</h3><p>util.callbackify(original)方法将 async 异步函数(或者一个返回值为 Promise 的函数)转换成遵循 Node.js 回调风格的函数。 在回调函数中, 第一个参数 err 为 Promise rejected 的原因 (如果 Promise 状态为 resolved , err为 null ),第二个参数则是 Promise 状态为 resolved 时的返回值。例如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">'util'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">await</span> <span class="built_in">Promise</span>.resolve(<span class="string">'hello world'</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> callbackFunction = util.callbackify(fn);</span><br><span class="line"></span><br><span class="line">callbackFunction(<span class="function">(<span class="params">err, ret</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) <span class="keyword">throw</span> err;</span><br><span class="line">  <span class="built_in">console</span>.log(ret);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// hello world</span></span><br></pre></td></tr></table></figure>
<p>注意：</p>
<ul>
<li>回调函数是异步执行的, 并且有异常堆栈错误追踪. 如果回调函数抛出一个异常, 进程会触发一个 ‘uncaughtException’ 异常, 如果没有被捕获, 进程将会退出。</li>
<li>null 在回调函数中作为一个参数有其特殊的意义, 如果回调函数的首个参数为 Promise rejected 的原因且带有返回值, 且值可以转换成布尔值 false, 这个值会被封装在 Error 对象里, 可以通过属性 reason 获取。</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(<span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> callbackFunction = util.callbackify(fn);</span><br><span class="line"></span><br><span class="line">callbackFunction(<span class="function">(<span class="params">err, ret</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 当Promise的rejecte是null时，它的Error与原始值都会被存储在'reason'中。</span></span><br><span class="line">  err &amp;&amp; err.hasOwnProperty(<span class="string">'reason'</span>) &amp;&amp; err.reason === <span class="literal">null</span>;  <span class="comment">// true</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h2 id="事件驱动-events"><a href="#事件驱动-events" class="headerlink" title="事件驱动  events"></a>事件驱动  events</h2><p>events是Node.js最重要的模块，原因是Node.js本身架构就是事件式的，大多数 Node.js 核心 API 都采用惯用的异步事件驱动架构，而它提供了唯一的接口，因此堪称Node.js事件编程的及时。events 模块不仅用于用户代码与 Node.js 下层事件循环的交互，还几乎被所有的模块依赖。</p>
<p>所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象开放了一个 eventEmitter.on() 函数，允许将一个或多个函数绑定到会被对象触发的命名事件上。 事件名称通常是驼峰式的字符串，但也可以使用任何有效的 JavaScript 属性名。对于每个事件， EventEmitter支持<br>若干个事件监听器。当事件发射时，注册到这个事件的事件监听器被依次调用，事件参数作<br>为回调函数参数传递。</p>
<p>例如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> EventEmitter = <span class="built_in">require</span>(<span class="string">'events'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyEmitter</span> <span class="keyword">extends</span> <span class="title">EventEmitter</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> myEmitter = <span class="keyword">new</span> MyEmitter();</span><br><span class="line"><span class="comment">// eventEmitter.on() 方法用于注册监听器</span></span><br><span class="line">myEmitter.on(<span class="string">'event'</span>, () =&gt; &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'触发了一个事件！'</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// eventEmitter.emit() 方法用于触发事件</span></span><br><span class="line">myEmitter.emit(<span class="string">'event'</span>);</span><br></pre></td></tr></table></figure>
<p>下面让我们来看看EventEmitter最常用的API：</p>
<ul>
<li>EventEmitter.on(event, listener) 方法可以添加 listener 函数到名为 eventName 的事件的监听器数组的末尾。不会检查 listener 是否已被添加。多次调用并传入相同的 eventName 和 listener 会导致 listener 被添加与调用多次。</li>
<li>emitter.prependListener(eventName, listener)方法可以添加 listener 函数到名为 eventName 的事件的监听器数组的开头。 不会检查 listener 是否已被添加。 多次调用并传入相同的 eventName 和 listener 会导致 listener 被添加与调用多次。</li>
<li><p>eventEmitter.emit() 方法允许将任意参数传给监听器函数。当一个普通的监听器函数被 EventEmitter 调用时，标准的 this 关键词会被设置指向监听器所附加的 EventEmitter。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 实例：</span></span><br><span class="line"><span class="keyword">const</span> myEE = <span class="keyword">new</span> EventEmitter();</span><br><span class="line">myEE.on(<span class="string">'foo'</span>, () =&gt; <span class="built_in">console</span>.log(<span class="string">'a'</span>));</span><br><span class="line">myEE.prependListener(<span class="string">'foo'</span>, () =&gt; <span class="built_in">console</span>.log(<span class="string">'b'</span>));</span><br><span class="line">myEE.emit(<span class="string">'foo'</span>);</span><br><span class="line"><span class="comment">// 打印:</span></span><br><span class="line"><span class="comment">//   b</span></span><br><span class="line"><span class="comment">//   a</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>EventEmitter.once(event, listener)  为指定事件注册一个单次监听器，即监听器最多只会触发一次，触发后立刻解除该监听器。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">server.once(<span class="string">'connection'</span>, (stream) =&gt; &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'首次调用！'</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
</li>
<li><p>EventEmitter.removeListener(event, listener) 移除指定事件的某个监听器， listener 必须是该事件已经注册过的监听器。（注意：removeListener 最多只会从监听器数组里移除一个监听器实例。 如果任何单一的监听器被多次添加到指定 eventName 的监听器数组中，则必须多次调用 removeListener 才能移除每个实例。）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">const callback = (stream) =&gt; &#123;</span><br><span class="line">  console.log(&apos;有连接！&apos;);</span><br><span class="line">&#125;;</span><br><span class="line">server.on(&apos;connection&apos;, callback);</span><br><span class="line">// ...</span><br><span class="line">server.removeListener(&apos;connection&apos;, callback);</span><br></pre></td></tr></table></figure>
</li>
<li><p>EventEmitter.removeAllListeners([event]) 移除所有事件的所有监听器，如果指定  event ，则移除指定事件的所有监听器</p>
</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> callback = <span class="function">(<span class="params">stream</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'有连接！'</span>);</span><br><span class="line">&#125;;</span><br><span class="line">server.on(<span class="string">'connection'</span>, callback);</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">server.removeListener(<span class="string">'connection'</span>, callback);</span><br></pre></td></tr></table></figure>
<h3 id="error-事件"><a href="#error-事件" class="headerlink" title="error 事件"></a>error 事件</h3><p>EventEmitter 定义了一个特殊的事件  error ，它包含了“错误”的语义，我们在遇到异常的时候通常会发射 error 事件。当 error被发射时，EventEmitter规定如果没有响<br>应的监听器，Node.js 会把它当作异常，退出程序并打印调用栈。我们一般要为会发射 error 事件的对象设置监听器，避免遇到错误后整个程序崩溃。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> events = <span class="built_in">require</span>(<span class="string">'events'</span>);</span><br><span class="line"><span class="keyword">var</span> emitter = <span class="keyword">new</span> events.EventEmitter();</span><br><span class="line">emitter.emit(<span class="string">'error'</span>);</span><br></pre></td></tr></table></figure>
<h3 id="继承EventEmitter"><a href="#继承EventEmitter" class="headerlink" title="继承EventEmitter"></a>继承EventEmitter</h3><p>大多数情况下，我们不会直接使用EventEmitter，而是在对象中继承它，包括fs,http在内的只要是支持事件响应的核心模块都是EventEmitter的子类。这样做的原因有以下两个：</p>
<ul>
<li>具有某个实体功能的对象实现事件符合语义，事件的监听和发射应该是一个对象的方法。</li>
<li>JavaScript 的对象机制是基于原型的，支持<br>部分多重继承，继承  EventEmitter 不会打乱对象原有的继承关系。</li>
</ul>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/Node.js核心入门（二）/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/Node.js核心入门（二）/" itemprop="url">Node.js核心入门（二）</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/Node-js/" itemprop="url" rel="index">
                    <span itemprop="name">Node.js</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  4,569
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  19
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>目录：<br><a href="https://juejin.im/entry/5ac0e2265188255c272212d4" target="_blank" rel="noopener">Node.js核心入门(一)</a></p>
<ul>
<li>全局对象</li>
<li>常用工具</li>
<li>事件机制</li>
</ul>
<p>Node.js核心入门（二）</p>
<ul>
<li>文件系统访问</li>
<li>HTTP服务器与客户端<h2 id="文件系统-fs"><a href="#文件系统-fs" class="headerlink" title="文件系统 fs"></a>文件系统 fs</h2>fs 模块是文件操作的封装，它提供了文件的读取、写入、更名、删除、遍历目录、链接等 POSIX 文件系统操作，且所有的方法都有异步和同步的形式。异步方法的最后一个参数都是一个回调函数。 传给回调函数的参数取决于具体方法，但回调函数的第一个参数都会保留给异常。 如果操作成功完成，则第一个参数会是 null 或 undefined。</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line">fs.unlink(<span class="string">'/tmp/hello'</span>, (err) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) <span class="keyword">throw</span> err;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'成功删除 /tmp/hello'</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>当使用同步方法时，任何异常都会被立即抛出。 可以使用 try/catch 来处理异常，或让异常向上冒泡。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line">fs.unlinkSync(<span class="string">'/tmp/hello'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'成功删除 /tmp/hello'</span>);</span><br></pre></td></tr></table></figure>
<h3 id="1-fs-readFile-path-options-callback"><a href="#1-fs-readFile-path-options-callback" class="headerlink" title="1.fs.readFile(path,[options], callback)"></a>1.fs.readFile(path,[options], callback)</h3><p>fs.readFile(path,[options], callback) 是最简单的读取。它接受一个必选参数filename，表示要读取的文件名。第二个参数options是可选的，表示文件的字符编码。 callback 是回调函数，用于接收文件的内容。如果不指定 options ，则  callback 就是第二个参数。回调函数提供两个参数  err 和  data ， err 表示有没有错误发生，data 是文件内容。如果指定了  options， data 是一个解析后的字符串，否则  data 将会是以  Buffer形式表示的二进制数据。例如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fs.readFile(<span class="string">'/etc/passwd'</span>, <span class="string">'utf8'</span>, callback);</span><br></pre></td></tr></table></figure>
<p>需要注意的是，当 path 是一个目录时，fs.readFile() 与 fs.readFileSync() 的行为与平台有关。在 macOS、Linux 与 Windows 上，会返回一个错误。在 FreeBSD 上，会返回目录内容的表示。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在 macOS、Linux 与 Windows 上：</span></span><br><span class="line">fs.readFile(<span class="string">'&lt;directory&gt;'</span>, (err, data) =&gt; &#123;</span><br><span class="line">  <span class="comment">// =&gt; [Error: EISDIR: illegal operation on a directory, read &lt;directory&gt;]</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">//  在 FreeBSD 上：</span></span><br><span class="line">fs.readFile(<span class="string">'&lt;directory&gt;'</span>, (err, data) =&gt; &#123;</span><br><span class="line">  <span class="comment">// =&gt; null, &lt;data&gt;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="2-fs-readFileSync-path-options"><a href="#2-fs-readFileSync-path-options" class="headerlink" title="2.fs.readFileSync(path[, options])"></a>2.fs.readFileSync(path[, options])</h3><p>fs.readFileSync(filename, [encoding]) 是  fs.readFile 同步的版本。它接受的参数和  fs.readFile 相同，但读取到的文件内容会以函数返回值的形式返回。如果有错<br>误发生， fs 将会抛出异常，这时候我们就需要使用  try 和  catch 捕捉并处理异常。</p>
<h3 id="3-fs-open-path-flags-mode-callback"><a href="#3-fs-open-path-flags-mode-callback" class="headerlink" title="3.fs.open(path, flags[, mode], callback)"></a>3.fs.open(path, flags[, mode], callback)</h3><p>fs.open(path, flags[, mode], callback)是 POSIX  open 函数的<br>封装，与 C 语言标准库中的  fopen 函数类似。它接受两个必选参数， path 为文件的路径，<br>而flags 可以是以下值：</p>
<pre><code>&apos;r&apos; - 以读取模式打开文件。如果文件不存在则发生异常。

&apos;r+&apos; - 以读写模式打开文件。如果文件不存在则发生异常。

&apos;rs+&apos; - 以同步读写模式打开文件。命令操作系统绕过本地文件系统缓存。

（这对 NFS 挂载模式下打开文件很有用，因为它可以让你跳过潜在的旧本地缓存。 它对 I/O 的性能有明显的影响，所以除非需要，否则不要使用此标志。

注意，这不会使 fs.open() 进入同步阻塞调用。 如果那是你想要的，则应该使用 fs.openSync()。）

&apos;w&apos; - 以写入模式打开文件。文件会被创建（如果文件不存在）或截断（如果文件存在）。

&apos;wx&apos; - 类似 &apos;w&apos;，但如果 path 存在，则失败。

&apos;w+&apos; - 以读写模式打开文件。文件会被创建（如果文件不存在）或截断（如果文件存在）。

&apos;wx+&apos; - 类似 &apos;w+&apos;，但如果 path 存在，则失败。

&apos;a&apos; - 以追加模式打开文件。如果文件不存在，则会被创建。

&apos;ax&apos; - 类似于 &apos;a&apos;，但如果 path 存在，则失败。

&apos;a+&apos; - 以读取和追加模式打开文件。如果文件不存在，则会被创建。

&apos;ax+&apos; - 类似于 &apos;a+&apos;，但如果 path 存在，则失败。
</code></pre><p>mode 可设置文件模式（权限和 sticky 位），但只有当文件被创建时才有效。默认为 0o666，可读写。</p>
<h3 id="4-fs-read-fd-buffer-offset-length-position-callback"><a href="#4-fs-read-fd-buffer-offset-length-position-callback" class="headerlink" title="4.fs.read(fd, buffer, offset, length, position, callback)"></a>4.fs.read(fd, buffer, offset, length, position, callback)</h3><p>fs.read(fd, buffer, offset, length, position, callback) 是 POSIX  read 函数的封装，相比  fs.readFile 提供了更底层的接口。从 fd 指定的文件中读取数据。buffer 是数据将被写入到的 buffer。offset 是 buffer 中开始写入的偏移量。length是一个整数，指定要读取的字节数。position指定从文件中开始读取的位置。如果position为null，则数据从当前文件读取位置开始读取，且文件读取位置会被更新。 如果 position 为一个整数，则文件读取位置保持不变。回调有三个参数 (err, bytesRead, buffer)。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line">fs.open(<span class="string">'content.txt'</span>, <span class="string">'r'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, fd</span>) </span>&#123;</span><br><span class="line"><span class="keyword">if</span> (err) &#123;</span><br><span class="line"><span class="built_in">console</span>.error(err);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> buf = <span class="keyword">new</span> Buffer(<span class="number">8</span>);</span><br><span class="line">fs.read(fd, buf, <span class="number">0</span>, <span class="number">8</span>, <span class="literal">null</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, bytesRead, buffer</span>) </span>&#123;</span><br><span class="line"><span class="keyword">if</span> (err) &#123;</span><br><span class="line"><span class="built_in">console</span>.error(err);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'bytesRead: '</span> + bytesRead);</span><br><span class="line"><span class="built_in">console</span>.log(buffer);</span><br><span class="line">&#125;)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>输出：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">bytesRead: <span class="number">8</span></span><br><span class="line">&lt;Buffer <span class="number">54</span> <span class="number">65</span> <span class="number">78</span> <span class="number">74</span> <span class="number">20</span> e6 <span class="number">96</span> <span class="number">87</span>&gt;</span><br></pre></td></tr></table></figure>
<h2 id="HTTP服务器与客户端"><a href="#HTTP服务器与客户端" class="headerlink" title="HTTP服务器与客户端"></a>HTTP服务器与客户端</h2><p>Node.js 标准库提供了http模块，其中封装了一个高效的 HTTP 服务器和一个简易的HTTP 客户端。 http.Server 是一个基于事件的 HTTP 服务器，它的核心由 Node.js 下层 C++<br>部分实现，而接口由 JavaScript 封装，兼顾了高性能与简易性。 http.request则是一个HTTP 客户端工具，用于向 HTTP 服务器发起请求，例如实现 Pingback或者内容抓取。</p>
<p>Node.js 中的HTTP接口被设计成支持协议的许多特性。比如，大块编码的消息。这些接口不缓冲完整的请求或响应，用户能够以流的形式处理数据。HTTP消息头由一个对象表示，其中键名是小写的，键值不能修改：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123; <span class="string">'content-length'</span>: <span class="string">'123'</span>,</span><br><span class="line">  <span class="string">'content-type'</span>: <span class="string">'text/plain'</span>,</span><br><span class="line">  <span class="string">'connection'</span>: <span class="string">'keep-alive'</span>,</span><br><span class="line">  <span class="string">'host'</span>: <span class="string">'mysite.com'</span>,</span><br><span class="line">  <span class="string">'accept'</span>: <span class="string">'*/*'</span> &#125;</span><br></pre></td></tr></table></figure></p>
<p>为了支持各种可能的 HTTP 应用，Node.js的 HTTP API是非常底层的。它只涉及流处理与消息解析。它把一个消息解析成消息头和消息主体，但不解析具体的消息头或消息主体。键名是小写的，键值不能修改。为了支持各种可能的 HTTP 应用，Node.js 的 HTTP API 是非常底层的。 它只涉及流处理与消息解析。 它把一个消息解析成消息头和消息主体，但不解析具体的消息头或消息主体。</p>
<h3 id="HTTP服务器"><a href="#HTTP服务器" class="headerlink" title="HTTP服务器"></a>HTTP服务器</h3><p>http.Server 是  http  模块中的 HTTP 服务器对象，用 Node.js 做的所有基于 HTTP 协议的系统，如网站、社交应用甚至代理服务器，都是基于http.Server实现的。它提供了一套封装级别很低的API，仅仅是流控制和简单的学习解析，而所有的高级功能都是通过它的接口来实现的。比如官网上的这个例子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> hostname = <span class="string">'127.0.0.1'</span>;</span><br><span class="line"><span class="keyword">const</span> port = <span class="number">3000</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> server = http.createServer(<span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">  res.statusCode = <span class="number">200</span>;</span><br><span class="line">  res.setHeader(<span class="string">'Content-Type'</span>, <span class="string">'text/plain'</span>);</span><br><span class="line">  res.end(<span class="string">'Hello World\n'</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">server.listen(port, hostname, () =&gt; &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`Server running at http://<span class="subst">$&#123;hostname&#125;</span>:<span class="subst">$&#123;port&#125;</span>/`</span>);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>在这段代码中，就使用了http.createServer([requestListener])来新建一个的 http.Server 实例。现在就先让我们来看看http.createServer([requestListener])吧。</p>
<h4 id="1-http-Server-的事件"><a href="#1-http-Server-的事件" class="headerlink" title="1. http.Server 的事件"></a>1. http.Server 的事件</h4><p>http.Server 是一个基于事件的 HTTP 服务器，所有的请求都被封装为独立的事件，开发者只需要对它的事件编写响应函数即可实现 HTTP 服务器的所有功能。它继承自<br>EventEmitter ，提供了以下几个事件：</p>
<ul>
<li>request：每次接收到一个请求时触发。 注意，每个连接可能有多个请求（在 HTTP keep-alive 连接的情况下）。</li>
<li>connection ：当一个新的 TCP 流被建立时触发。socket 是一个 net.Socket 类型的对象。 通常用户无需访问该事件。 注意，因为协议解析器绑定到 socket 的方式，socket 不会触发 ‘readable’ 事件。socket 也可以通过 request.connection 访问。</li>
<li>connect：每当客户端发送 HTTP CONNECT 请求时触发。 如果该事件未被监听，则发送 CONNECT 请求的客户端会关闭连接。当该事件被触发后，请求的 socket 上没有 ‘data’ 事件监听器，这意味着需要绑定 ‘data’ 事件监听器，用来处理 socket 上被发送到服务器的数据。</li>
<li>close：当服务器关闭时，该事件被触发。注意不是在用户连接断开时，而是服务器关闭时。</li>
</ul>
<p>在这些事件最常用的是request是最常用的，因此  http 提供了一个捷径：<br>http.createServer([requestListener]) ，功能是创建一个 HTTP 服务器并将requestListener 作为  request 事件的监听函数。我们上面那个官网的例子就是如此，其实它显式的实现方法是这样的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//httpserver.js</span></span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">const</span> hostname = <span class="string">'127.0.0.1'</span>;</span><br><span class="line"><span class="keyword">const</span> port = <span class="number">3000</span>;</span><br><span class="line"><span class="keyword">const</span> server = <span class="keyword">new</span> http.Server();</span><br><span class="line">server.on(<span class="string">'request'</span>, (req, res) =&gt; &#123;</span><br><span class="line">  res.statusCode = <span class="number">200</span>;</span><br><span class="line">  res.setHeader(<span class="string">'Content-Type'</span>, <span class="string">'text/plain'</span>);</span><br><span class="line">  res.end(<span class="string">'Hello World\n'</span>);</span><br><span class="line">&#125;);</span><br><span class="line">server.listen(port, hostname, () =&gt; &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`Server running at http://<span class="subst">$&#123;hostname&#125;</span>:<span class="subst">$&#123;port&#125;</span>/`</span>);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<h4 id="2-http-ServerRequest"><a href="#2-http-ServerRequest" class="headerlink" title="2. http.ServerRequest"></a>2. http.ServerRequest</h4><p>http.ServerRequest  是 HTTP 请求的信息，是后端开发者最关注的内容。它一般由http.Server 的 request 事件发送，作为第一个参数传递，通常简称  request 或 HTTP 请求一般可以分为两部分：请求头（Request Header）和请求体（Requset Body）。以上内容由于长度较短都可以在请求头解析完成后立即读取。而请求体可能相对较长，需要一定的时间传输，因此  http.ServerRequest  提供了以下3个事件用于控制请求体传输。req。HTTP请求一般可以分为两部分：请求头（RequestHeader）和请求体（RequsetBody）。以上内容由于长度较短都可以在请求头解析完成后立即读取。而请求体可能相对较长，需要一定的时间传输，因此http.ServerRequest提供了以下3个事件用于控制请求体传输。http.ServerRequest提供了3个事件用于控制请求体传输：</p>
<ol>
<li>data：当请求体数据到来时，该事件被触发，提供一个参数给回调函数，是接受到的数据，该事件可能被多次调用（所有data按顺序的集合，是请求体数据）。如果该事件没有被监听，请求体将被抛弃；</li>
<li>end：当请求体数据完成时该事件触发。此后不再触发data事件；</li>
<li><p>close：用户当前请求结束时，该事件被触发。不同于end，如果用户强制终止了传输，也还是调用close。</p>
<pre><code>         表4-2  ServerRequest 的属性
名 称                  含 义
complete        客户端请求是否已经发送完成
httpVersion     HTTP 协议版本，通常是 1.0 或 1.1
method          HTTP 请求方法，如 GET、POST、PUT、DELETE 等
url             原始的请求路径，例如 /static/image/x.jpg 或 /user?name=byvoid
headers         HTTP 请求头
trailers        HTTP 请求尾（不常见）
connection      当前 HTTP 连接套接字，为 net.Socket 的实例
socket          connection 属性的别名
client          client 属性的别名
</code></pre><h4 id="3-获取-GET-请求内容"><a href="#3-获取-GET-请求内容" class="headerlink" title="3. 获取 GET 请求内容"></a>3. 获取 GET 请求内容</h4><p>注意， http.ServerRequest 提供的属性中没有类似于 PHP 语言中的 $_GET 或 $_POST 的属性，GET请求被直接内嵌在路径中。URL是完整的请求路径（包括？后面的部分），因此手动解析后面的内容作为GET请求的参数。Node.js的url模块中的parse函数提供了这个功能。</p>
</li>
</ol>
<p>以url：<a href="http://127.0.0.1/user?name=byvoid&amp;email=byvoid@byvoid.com为例：" target="_blank" rel="noopener">http://127.0.0.1/user?name=byvoid&amp;email=byvoid@byvoid.com为例：</a></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">"http"</span>);</span><br><span class="line"><span class="keyword">var</span> url = <span class="built_in">require</span>(<span class="string">"url"</span>);</span><br><span class="line"><span class="keyword">var</span> server = <span class="keyword">new</span> http.Server();</span><br><span class="line">server.on(<span class="string">"request"</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (req.url == <span class="string">"/favicon.ico"</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">var</span> m = url.parse(req.url, <span class="literal">true</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(m)</span><br><span class="line">    res.writeHead(<span class="number">200</span>, &#123;<span class="string">'Content-type'</span>: <span class="string">'text/html;charset = utf8'</span>&#125;);</span><br><span class="line">    res.end();</span><br><span class="line">&#125;)</span><br><span class="line">server.listen(<span class="number">80</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"The server begin"</span>);</span><br></pre></td></tr></table></figure>
<p>console.log输出内容：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Url &#123;</span><br><span class="line">  protocol: <span class="literal">null</span>,</span><br><span class="line">  slashes: <span class="literal">null</span>,</span><br><span class="line">  auth: <span class="literal">null</span>,</span><br><span class="line">  host: <span class="literal">null</span>,</span><br><span class="line">  port: <span class="literal">null</span>,</span><br><span class="line">  hostname: <span class="literal">null</span>,</span><br><span class="line">  hash: <span class="literal">null</span>,</span><br><span class="line">  search:<span class="string">'?name=byvoid&amp;email=byvoid@byvoid.com'</span>,</span><br><span class="line">  query: &#123; <span class="attr">name</span>: <span class="string">'byvoid'</span>, <span class="attr">email</span>:<span class="string">'byvoid@byvoid.com'</span> &#125;,</span><br><span class="line">  pathname: <span class="string">'/user'</span>,</span><br><span class="line">  path:<span class="string">'/user?name=byvoid&amp;email=byvoid@byvoid.com'</span>,</span><br><span class="line">  href:<span class="string">'/user?name=byvoid&amp;email=byvoid@byvoid.com'</span> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h4 id="4-获取-POST-请求内容"><a href="#4-获取-POST-请求内容" class="headerlink" title="4. 获取 POST 请求内容"></a>4. 获取 POST 请求内容</h4><p>HTTP 协议1.1版本提供了8种标准的请求方法，而其中最常见的就是 GET 和 POST。相比GET请求把所有的内容编码到访问路径中，POST 请求的内容全部都在请求体中。http.ServerRequest 并没有一个属性内容是在请求体中，原因是等待请求体传输可能是一件耗时的工作，譬如上传文件。而很多时候我们可能并不需要理会请求体的内容，且恶意的 POST<br>请求会大大消耗服务器的资源。所以 Node.js 默认是不会解析请求体的，因此当我们需要的时候，我们就要手写一个，具体实现方法如下：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">var</span> querystring = <span class="built_in">require</span>(<span class="string">'querystring'</span>);</span><br><span class="line"><span class="keyword">var</span> util = <span class="built_in">require</span>(<span class="string">'util'</span>);</span><br><span class="line">http.createServer(<span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> post = <span class="string">''</span>;</span><br><span class="line">    req.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">chunk</span>) </span>&#123;</span><br><span class="line">    post += chunk;</span><br><span class="line">&#125;);</span><br><span class="line">req.on(<span class="string">'end'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    post = querystring.parse(post);</span><br><span class="line">    res.end(util.inspect(post));</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;).listen(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure></p>
<h4 id="5-http-ServerResponse"><a href="#5-http-ServerResponse" class="headerlink" title="5.http.ServerResponse"></a>5.http.ServerResponse</h4><p>http.ServerResponse  是返回给客户端的信息，决定了用户最终能看到的结果。它也是由  http.Server 的  request  事件发送的，作为第二个参数传递，一般简称为<br>response 或 res 。http.ServerResponse 有三个重要的成员函数，用于返回响应头、响应内容以及结束请求：</p>
<ul>
<li>response.writeHead(statusCode, [headers]) ：向请求的客户端发送响应头。statusCode是HTTP状态码，如200（请求成功）、404（未找到）等。headers是一个类似关联数组的对象，表示响应头的每个属性。该函数在一个请求内最多只能调用一次，如果不调用，则会自动生成一个响应头。</li>
<li>response.write(data, [encoding]) ：向请求的客户端发送响应内容。 data 是一个  Buffer 或字符串，表示要发送的内容。如果  data 是字符串，那么需要指定<br>encoding 来说明它的编码方式，默认是 utf-8 。在 response.end 调用之前，response.write  可以被多次调用。</li>
<li>response.end([data], [encoding]) ：结束响应，告知客户端所有发送已经完成。当所有要返回的内容发送完毕的时候，该函数 必须 被调用一次。它接受两个可选参数，意义和 response.write  相同。如果不调用该函数，客户端将永远处于等待状态。</li>
</ul>
<h3 id="HTTP-客户端"><a href="#HTTP-客户端" class="headerlink" title="HTTP 客户端"></a>HTTP 客户端</h3><p>http 模块提供了两个函数 http.request和http.get，功能是作为客户端向HTTP服务器发起请求。</p>
<h4 id="1-http-request-options-callback"><a href="#1-http-request-options-callback" class="headerlink" title="1.http.request(options,callback)"></a>1.http.request(options,callback)</h4><p>http.request(options,callback)发起HTTP请求，它接受两个参数，option是一个类似关联数组的对象，表示请求的参数，callback是请求的回调函数。option常用的参数如下所示：</p>
<ul>
<li>host ：请求网站的域名或 IP 地址。</li>
<li>port ：请求网站的端口，默认 80。</li>
<li>method ：请求方法，默认是 GET。</li>
<li>path ：请求的相对于根的路径，默认是“ / ”。 QueryString  应该包含在其中。例如  /search?query=byvoid 。</li>
<li>headers ：一个关联数组对象，为请求头的内容。</li>
</ul>
<p>而callback 则传递一个参数，为 http.ClientResponse 的实例。http.request 返回一个http.ClientRequest 的实例，下面是一个通过 http.request  发送 POST 请求的代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">var</span> querystring = <span class="built_in">require</span>(<span class="string">'querystring'</span>);</span><br><span class="line"><span class="keyword">var</span> contents = querystring.stringify(&#123;</span><br><span class="line">    name: <span class="string">'byvoid'</span>,</span><br><span class="line">    email: <span class="string">'byvoid@byvoid.com'</span>,</span><br><span class="line">    address: <span class="string">'Zijing 2#, Tsinghua University'</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="keyword">var</span> options = &#123;</span><br><span class="line">    host: <span class="string">'www.byvoid.com'</span>,</span><br><span class="line">    path: <span class="string">'/application/node/post.php'</span>,</span><br><span class="line">    method: <span class="string">'POST'</span>,</span><br><span class="line">    headers: &#123;</span><br><span class="line">        <span class="string">'Content-Type'</span>: <span class="string">'application/x-www-form-urlencoded'</span>,</span><br><span class="line">        <span class="string">'Content-Length'</span> : contents.length</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> req = http.request(options, <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    res.setEncoding(<span class="string">'utf8'</span>);</span><br><span class="line">    res.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(data);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line">req.write(contents);</span><br><span class="line">req.end();</span><br></pre></td></tr></table></figure>
<p>运行结果如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">array(<span class="number">3</span>) &#123;</span><br><span class="line">[<span class="string">"name"</span>]=&gt;</span><br><span class="line">string(<span class="number">6</span>) <span class="string">"byvoid"</span></span><br><span class="line">[<span class="string">"email"</span>]=&gt;</span><br><span class="line">string(<span class="number">17</span>) <span class="string">"byvoid@byvoid.com"</span></span><br><span class="line">[<span class="string">"address"</span>]=&gt;</span><br><span class="line">string(<span class="number">30</span>) <span class="string">"Zijing 2#, Tsinghua University"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="2-http-get-options-callback"><a href="#2-http-get-options-callback" class="headerlink" title="2.http.get(options, callback)"></a>2.http.get(options, callback)</h4><p>http 模块还提供了一个更加简便的方法用于处理GET请求：http.get(options, callback)。它是http.request的简化版，唯一的区别在于http.get自动将请求方法设为了 GET 请求，同时不需要手动调用 req.end() ：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line">http.get(&#123;<span class="attr">host</span>: <span class="string">'www.byvoid.com'</span>&#125;, <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    res.setEncoding(<span class="string">'utf8'</span>);</span><br><span class="line">    res.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(data);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h4 id="http-ClientRequest"><a href="#http-ClientRequest" class="headerlink" title="http.ClientRequest"></a>http.ClientRequest</h4><p>该对象在 http.request() 内部被创建并返回。它表示着一个正在处理的请求，其请求头已进入队列。它提供一个response事件，即http.request或http.get第二个参数指定的回调函数的绑定对象。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">var</span> req = http.get(&#123;<span class="attr">host</span>: <span class="string">'www.byvoid.com'</span>&#125;);</span><br><span class="line">    req.on(<span class="string">'response'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    res.setEncoding(<span class="string">'utf8'</span>);</span><br><span class="line">    res.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(data);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>http.ClientRequest像http.ServerResponse一样也提供了  write 和 end 函数，用于向服务器发送请求体，通常用于 POST、PUT 等操作。所有写结束以后必须调用 end<br>函数以通知服务器，否则请求无效。http.ClientRequest  还提供了以下常用的函数:</p>
<ul>
<li>request.abort() ：标记请求为终止。 调用该方法将使响应中剩余的数据被丢弃且 socket 被销毁。</li>
<li>request.setTimeout(timeout,[callback])：设置请求超时时间， timeout为毫秒数。一旦socket被分配给请求且已连接，socket.setTimeout() 会被调用。</li>
<li>request.end([data[, encoding]][, callback])结束发送请求。如果部分请求主体还未被发送，则会刷新它们到流中。 如果请求是分块的，则会发送终止字符 ‘0\r\n\r\n’。</li>
</ul>
<h4 id="http-ClientResponse"><a href="#http-ClientResponse" class="headerlink" title="http.ClientResponse"></a>http.ClientResponse</h4><p>http.ClientResponse 与  http.ServerRequest 相似，提供了三个事件 data 、end和 close，分别在数据到达、传输结束和连接结束时触发，其中  data 事件传递一个参数chunk ，表示接收到的数据。</p>
<p>http.ClientResponse  也提供了一些属性，用于表示请求的结果状态：</p>
<pre><code>statusCode   HTTP 状态码，如 200、404、500
httpVersion  HTTP 协议版本，通常是 1.0 或 1.1
headers      HTTP 请求头
trailers     HTTP 请求尾（不常见）
</code></pre><p>http.ClientResponse 还提供了以下几个特殊的函数：</p>
<ul>
<li>response.setEncoding([encoding]) ：设置默认的编码，当  data 事件被触发时，数据将会以encoding编码。默认值是null，即不编码，以  Buffer 的形式存储。常用编码为 utf8。</li>
<li>response.pause()：暂停接收数据和发送事件，方便实现下载功能。</li>
<li>response.resume() ：从暂停的状态中恢复。</li>
</ul>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/JavaScript异步编程基础/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/JavaScript异步编程基础/" itemprop="url">JavaScript异步编程基础</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/JavaScript/" itemprop="url" rel="index">
                    <span itemprop="name">JavaScript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  2,201
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  8
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h3 id="单线程的JavaScript"><a href="#单线程的JavaScript" class="headerlink" title="单线程的JavaScript"></a>单线程的JavaScript</h3><p>大佬们在初学JavaScript的时候应该或多或少都知道JavaScript是一门单线程的弱类型语言。而JavaScript之所以是单线程其实与它的用途有关：由于JavaScript的早期的主要用途是与用户互动，以及操作DOM，因此如果设计成多线程并行就会带来很多复杂和不可控的同步问题，比如当两个不同的线程一个是要往一个节点添加内容，另一个则是要删除这个节点，这时浏览器就懵逼了。</p>
<p>因为JavaScript是单线程的，因此这就意味着，JavaScript中所有的任务都需要排队依次完成，这样说起来很简单，但很多时候我们在写程序的时候就会意识到一个问题，那就是程序中将来执行的代码并不一定在现在运行的部分执行完之后就立即执行。比如这样：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">&#125;，<span class="number">1000</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1 3 2</span></span><br></pre></td></tr></table></figure></p>
<p>看到上面的代码，很多只想着单线程的哥们可能会毫不犹豫的大喊：1 2 3！但现实总是骨感的，人生如此，爱情如此，代码也是如此。正确的打印顺序应该是1 3 2。这是由于JavaScript的设计者在设计之初就料想，单线程可能会由于运算量过大或执行耗时过长，而后面的任务却只能痴痴等待，这就会导致我们常说的IO操作（耗时但CPU处于闲置状态）。因此JavaScript设计者就将所有任务分以下两种以解决这种问题：</p>
<ol>
<li>同步任务（在主线程中只有前面的代码执行完毕后，后面的才能执行）</li>
<li>异步任务（从主线程提出，进入任务队列执行，等执行完毕后通知主线程，这个异步任务可以执行了，才会进入主线程执行）</li>
</ol>
<p>主线程与任务队列的示意图如下（转自阮一峰老师的<a href="http://www.ruanyifeng.com/blog/2014/10/event-loop.html/" target="_blank" rel="noopener">JavaScript 运行机制详解：再谈Event Loop</a>）：</p>
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2014/bg2014100801.jpg" alt="image"></p>
<h3 id="事件循环（Event-Loop）"><a href="#事件循环（Event-Loop）" class="headerlink" title="事件循环（Event Loop）"></a>事件循环（Event Loop）</h3><p>前面我们提到，当JavaScript在异步任务完成后会通知主线程该任务可以执行了，那么又是如何 通知的呢？其实用一句话就可以去描述这个过程了：</p>
<blockquote>
<p>工作线程将信息放到任务队列中，主线程则通事件循环过程去取完成的消息。</p>
</blockquote>
<p>让我们再来看这段代码：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">&#125;，<span class="number">1000</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1 3 2</span></span><br></pre></td></tr></table></figure></p>
<p>其实它会经历一下几个步骤：</p>
<ol>
<li>打印 1</li>
<li>调用 setTimeout，发现是一个异步任务，从主线程提出，进行异步运行。</li>
<li>打印 3</li>
<li>异步任务运行完毕，工作线程在任务队列中放置一个事件。</li>
<li>主线程所有同步事件执行完毕后，通过事件循环读取事件，将异步任务放入主线程最后端。</li>
<li>打印 2</li>
</ol>
<p>总结起来，JavaScript的代码执行机制其实就三点：</p>
<ul>
<li>所有同步任务都在主线程上执行，形成一个执行栈（execution context stack）。</li>
<li>主线程之外，还存在一个”任务队列”（task queue）。只要异步任务有了运行结果，就在”任务队列”之中放置一个事件。</li>
<li>一旦主线程的栈中的所有同步任务执行完毕，系统就会读取任务队列，选择需要首先执行的任务然后执行。</li>
</ul>
<p>实际上，主线程只会做一件事情，就是从任务队列里面取事件、执行事件，执行完毕；再取事件、再执行事件…这样不断取事件，执行事件的循环机制就叫做事件循环机制。（需要注意的的是当任务队列为空时，就会等待直到任务队列变成非空。）其基本逻辑如下：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (queue.waitForMessage()) &#123;</span><br><span class="line">  queue.processNextMessage();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="常见的异步事件"><a href="#常见的异步事件" class="headerlink" title="常见的异步事件"></a>常见的异步事件</h3><p>在我们的日常开发中比较常见的异步事件主要是以下三种：</p>
<ul>
<li>DOM操作（在用户执行操作后进入任务队列）</li>
<li>网络请求（在网络响应后进入任务队列）</li>
<li>定时器（在规定时间到达后进入任务队列）</li>
</ul>
<p>现在让我们在看看具体的实例把：</p>
<h4 id="DOM操作"><a href="#DOM操作" class="headerlink" title="DOM操作"></a>DOM操作</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'btn'</span>).addEventListener(<span class="string">'click'</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//1</span></span><br><span class="line"><span class="comment">//3</span></span><br><span class="line"><span class="comment">//点击后 </span></span><br><span class="line"><span class="comment">//2</span></span><br></pre></td></tr></table></figure>
<p>上面的代码很容易理解，先后打印1和3。当用户进行点击后·才会执行函数，打印2。</p>
<h4 id="网络请求"><a href="#网络请求" class="headerlink" title="网络请求"></a>网络请求</h4><p>网络请求方面,通常我们可以将其分为两种情况:</p>
<ul>
<li>进行ajax请求</li>
<li>动态<img>加载</li>
</ul>
<p>先让我们来看看ajax请求：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'1'</span>)；</span><br><span class="line">$.get(<span class="string">''</span>./wozeishuai.json<span class="string">',function(data) &#123;</span></span><br><span class="line"><span class="string">    console.log(data);</span></span><br><span class="line"><span class="string">&#125;);</span></span><br><span class="line"><span class="string">console.log(2);</span></span><br><span class="line"><span class="string">// 1</span></span><br><span class="line"><span class="string">// 2</span></span><br><span class="line"><span class="string">// ?</span></span><br></pre></td></tr></table></figure>
<p>首先两个同步任务会被执行，会依次打印1和2。而data则会视情况而定，如果ajax请求成功，那么就会在2后面被打印出来；但如果请求不成功，那么就不会被打印出来。</p>
<p>还有就是动态的<img>的加载了，在这方面我们可能会遇到：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"><span class="keyword">let</span> img = <span class="built_in">document</span>.creatElement(<span class="string">'img'</span>);</span><br><span class="line">img.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>)</span><br><span class="line">&#125;;</span><br><span class="line">img.src = <span class="string">'/sky.png'</span>;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"><span class="comment">//1</span></span><br><span class="line"><span class="comment">//3</span></span><br><span class="line"><span class="comment">//?</span></span><br></pre></td></tr></table></figure>
<p>同理，当我们执行上面的代码的时候，我们首先依次打印出1,3。然后就需要等待img的加载了，这同样需要一个过程。如果加载成功就会打印出2，如果加载失败，那么2就不会被打印出来。对于img的加载问题，通常我们还有可能遇到这种情况：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementsByTagNames(<span class="string">'img'</span>)[<span class="number">0</span>].width</span><br></pre></td></tr></table></figure>
<p>乍看起来这段代码并没有什么问题，好像并没有存在异步的问题，一切都应该是你期望的那样进行着。但当你执行时，却惊讶的发现，取得的竟然width是0！然后整个人都不好了，心态崩了，怎么也想不明白为什么会是这样（没错就是刚学编程的我）。其实这个问题也很好理解，因为<img>的加载是需要时间的，因此会被浏览器归入异步事件之中，而这条语句是同步语句，会被主线程依次执行，而当这条语句执行完毕后，img才会被加载完成进入主线程。因此我们可以使用异步改写代码，使其能够取到正确的width:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementsByTagNames(<span class="string">'img'</span>)[<span class="number">0</span>].onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">this</span>.width)；  <span class="comment">//打印width</span></span><br><span class="line">&#125;；</span><br></pre></td></tr></table></figure>
<h4 id="定时器"><a href="#定时器" class="headerlink" title="定时器"></a>定时器</h4><p>定时器我们在上面的示例中已经有提到了，就是这个：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">&#125;，<span class="number">1000</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1 3 2</span></span><br></pre></td></tr></table></figure>
<p>对于定时器，主要有以下三个用处：</p>
<ul>
<li>让浏览器渲染当前的变化（很多浏览器UI渲染和JavaScript执行是放在一个线程中，当线程阻塞时会导致界面无法更新渲染）</li>
<li>重新评估”script is running too long”警告</li>
<li>改变代码的执行顺序</li>
</ul>
<p>还有一点我们得要额外注意。那就是当在零延迟调用 setTimeout 时，它并不会是真正的零延迟，它的调用取决于队列里正在等待的消息数量。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">  setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">cb</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">  setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">cb1</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">4</span>);</span><br><span class="line">  &#125;, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">&#125;)();</span><br><span class="line"></span><br><span class="line"><span class="comment">//1 3 5 2 4</span></span><br></pre></td></tr></table></figure>
<h3 id="其他要点"><a href="#其他要点" class="headerlink" title="其他要点"></a>其他要点</h3><h4 id="浏览器不是单线程的"><a href="#浏览器不是单线程的" class="headerlink" title="浏览器不是单线程的"></a>浏览器不是单线程的</h4><p>虽然JavaScript通常运行在浏览器中，且是单线程的，且每个window都有一个JavaScript线程。但浏览器并不是单线程的，例如Webkit或是Gecko引擎，都可能有如下线程：</p>
<ul>
<li>javascript引擎线程</li>
<li>浏览器UI渲染线程</li>
<li>浏览器事件触发线程</li>
<li>HTTP请求线程</li>
</ul>
<h4 id="阻塞问题"><a href="#阻塞问题" class="headerlink" title="阻塞问题"></a>阻塞问题</h4><p>因为JavaScript处理 I/O 通常通过事件和回调来执行，所以当一个应用正等待IndexedDB查询返回或者一个 XHR 请求返回时，它仍然可以处理其它事情，所以通常来说JavaScript是不会出现阻塞的。但凡事都有例外，比如这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">alert(<span class="string">'hello,world'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">2</span>)</span><br></pre></td></tr></table></figure>
<p>执行上面的代码的时候，它并不会依次执行下去，而是先打印，然后跳出一个弹窗，只有当你点击确定之后，才会执行后面的代码打印2出来。具有这种阻塞效果的有alert之类的弹窗和同步XHR，这需要注意和避免。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/详解undefined和null的区别/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/详解undefined和null的区别/" itemprop="url">详解undefined和null的区别</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/JavaScript/" itemprop="url" rel="index">
                    <span itemprop="name">JavaScript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  588
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  2
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p><em>对于undefined和null我一直知道他们有很多区别，也知道一点关于他们的区别，但却不具体系统，因此总结了一下，主要心得如下：</em></p>
<p>要区分它们，首先要明确这两个值所代表的意义：</p>
<ul>
<li><p>null 是一个 JavaScript字面量，表示空值，即没有对象被呈现，它是JavaScript的原始值之一，使用typeof运算得到 “object”。</p>
</li>
<li><p>undefined 是全局对象的一个属性，当声明了一个变量而未初始化时，得到的就是undefined。</p>
</li>
</ul>
<p>具体表现如下:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">a;</span><br><span class="line"><span class="built_in">console</span>.log(a);</span><br><span class="line"><span class="comment">//Uncaught ReferenceError: a is not defined</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> b = <span class="literal">null</span>;</span><br><span class="line"><span class="built_in">console</span>.log(b); <span class="comment">//null</span></span><br></pre></td></tr></table></figure>
<p>而它们出现的情景也各不相同，具体总结如下：</p>
<p><strong>null</strong>:即表示为空，没有对象。</p>
<ol>
<li>作为函数的参数，表示该函数的参数不是对象。</li>
<li>作为对象原型链的终点。</li>
</ol>
<p><strong>undefined</strong>：表示缺少值，此处应该有值，但未定义。</p>
<ol>
<li>变量被声明了，但没有值，此时为undefined</li>
<li>调用函数时，应该提供的参数没有提供，此时为undefined</li>
<li>对象没有赋值的属性，此属性值为undefined</li>
<li>函数没有返回值，默认返回undefined</li>
</ol>
<h4 id="null与undefined的区别主要体现在以下三点"><a href="#null与undefined的区别主要体现在以下三点" class="headerlink" title="null与undefined的区别主要体现在以下三点:"></a>null与undefined的区别主要体现在以下三点:</h4><h6 id="1-值相等而类型不相等"><a href="#1-值相等而类型不相等" class="headerlink" title="1.值相等而类型不相等"></a>1.值相等而类型不相等</h6><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x;</span><br><span class="line"><span class="keyword">var</span> y = <span class="literal">null</span>;</span><br><span class="line"><span class="built_in">console</span>.log(x == y); <span class="comment">//ture</span></span><br><span class="line"><span class="built_in">console</span>.log(x ===y); <span class="comment">//false</span></span><br></pre></td></tr></table></figure>
<p>在上面的代码中 == 会继续类型转换，且值是相等的，所以会得出 ture；而在<br>=== 中则不会发生类型转换所以得出的是 false。因此undefined和null的值相等而类型不相等。</p>
<h6 id="2-数字运算不相同"><a href="#2-数字运算不相同" class="headerlink" title="2.数字运算不相同"></a>2.数字运算不相同</h6><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>+<span class="literal">null</span> </span><br><span class="line"><span class="comment">//1</span></span><br><span class="line"></span><br><span class="line"><span class="number">1</span>+<span class="literal">undefined</span> </span><br><span class="line"><span class="comment">//NaN</span></span><br></pre></td></tr></table></figure>
<p>这是由于约定的不同所决定的。在规定中数字与undefined运算时，结果都NaN。在与null运算时，null始终为0。</p>
<h6 id="3-具体作法不一样"><a href="#3-具体作法不一样" class="headerlink" title="3.具体作法不一样"></a>3.具体作法不一样</h6><p>当我们想要判断一个值是否存在可以这样写：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">objA.valueA === <span class="literal">undefined</span></span><br></pre></td></tr></table></figure>
<p>而当我们想要判断一个对象是否为空时可以这么写：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">objA.valueA == <span class="literal">null</span></span><br></pre></td></tr></table></figure>
<p>最后借一波MDN总结的null和undefined的区别的代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="literal">null</span>        <span class="comment">// object (因为一些以前的原因而不是'null')</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">undefined</span>   <span class="comment">// undefined</span></span><br><span class="line"><span class="literal">null</span> === <span class="literal">undefined</span> <span class="comment">// false</span></span><br><span class="line"><span class="literal">null</span>  == <span class="literal">undefined</span> <span class="comment">// true</span></span><br><span class="line"><span class="literal">null</span> === <span class="literal">null</span> <span class="comment">// true</span></span><br><span class="line"><span class="literal">null</span> == <span class="literal">null</span> <span class="comment">// true</span></span><br><span class="line">!<span class="literal">null</span> <span class="comment">//true</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="number">1</span> + <span class="literal">null</span>) <span class="comment">// false</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="number">1</span> + <span class="literal">undefined</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/详解this绑定/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/详解this绑定/" itemprop="url">详解this绑定</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/JavaScript/" itemprop="url" rel="index">
                    <span itemprop="name">JavaScript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  1,542
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  7
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>与python等其他的编程语言相比，this 关键字在 JavaScript 中的表现有所不同，是否是严格模式也会影响this的绑定；这里面套路众多，一不小心就可能出错，而且this作为动态作用域的表亲，与JavaScript的词法作用域差别很大，因此很多人称它为JavaScript的一个大坑。（我也是这样认为的，2333）</p>
<p>在绝大多数情况下，函数的调用方式决定了this的值。this不能在执行期间被赋值，并且在每次函数被调用时this的值也可能会不同。在此我们主要讨论一下较为常见的几种调用方式和在ES6中的箭头函数中的this。</p>
<p>注：这篇文章是我写的最早的文章之一了是个笔记，因此有点啰嗦和局限性，看完《你不知道的JavaScript》后，总结起来this大致也就四种情况：</p>
<ul>
<li>默认绑定（window）</li>
<li>隐式绑定(对象的方法)</li>
<li>显示绑定（call,apply,bind）</li>
<li>new绑定</li>
</ul>
<p>优先级自下而上。而且在平常日常开发中也尽量少用this（少用，不代表不用），以免在他人（或者很久以后的自己）在维护代码的时候理解错误~（箭头函数是个好东西）</p>
<h3 id="几种较为常见的调用方式："><a href="#几种较为常见的调用方式：" class="headerlink" title="几种较为常见的调用方式："></a>几种较为常见的调用方式：</h3><p>1.在全局范围内使用this，它将会指向全局对象即window.</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">this</span>.a);  <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">this</span> === <span class="built_in">window</span>); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">this</span>.b = <span class="string">"hello,world"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">window</span>.b);  <span class="comment">// "hello,world"</span></span><br><span class="line"><span class="built_in">console</span>.log(b);         <span class="comment">// "hello,world"</span></span><br></pre></td></tr></table></figure>
<p>2.在函数中调用时，也会指向全局对象。而在node中则指向global：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(a);</span><br><span class="line">&#125;</span><br><span class="line">fn(); <span class="comment">//1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//所以要注意的是：</span></span><br><span class="line"><span class="comment">//在浏览器中：</span></span><br><span class="line">fn() === <span class="built_in">window</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//在Node中：</span></span><br><span class="line">fn() === global;</span><br></pre></td></tr></table></figure>
<p>要注意的是在ES5的严格模式下，不存在全局变量。this将保持他进入执行上下文时的值，这种情况下this将会是undefined:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">"use strict"</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">a</span>)</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br><span class="line">fn(<span class="number">1</span>);   <span class="comment">//undefined</span></span><br><span class="line"></span><br><span class="line">fn() === <span class="literal">undefined</span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
<p>3.方法调用text.foo(); 在这个例子时this指向text对象。要注意的是这样的行为，根本不受函数定义方式或位置的影响。而且this 的绑定只受最靠近的成员引用的影响</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> text = &#123;</span><br><span class="line">  num: <span class="number">1</span>,</span><br><span class="line">  foo: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.num;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(text.foo()); <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line">fn = text.foo;</span><br><span class="line">text.a = &#123;<span class="attr">b</span>: fn, <span class="attr">num</span>: <span class="number">2</span>&#125;;</span><br><span class="line"><span class="built_in">console</span>.log(text.a.b()); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure>
<p>4.当一个函数用作构造函数时，它的this就会被绑定到正在构造的新对象中：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Person</span>(<span class="params">age</span>)</span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.age = age;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> jake = <span class="keyword">new</span> Person(<span class="number">18</span>);</span><br><span class="line">jake.age; <span class="comment">//18</span></span><br></pre></td></tr></table></figure>
<p>5.当使用call或者apply方法时，函数内的this将会被显式设置为函数调用的第一个参数。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//对象也可以作为call和apply传入的第一个参数，且this会显式的设置为该对象</span></span><br><span class="line"><span class="keyword">var</span> obj = &#123;<span class="attr">a</span>: <span class="number">1</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这个属性是在global对象定义的。</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="comment">// this的值取决于函数的调用方式</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>.a;  </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fn();          <span class="comment">// 2</span></span><br><span class="line">fn.call(obj);  <span class="comment">// 1</span></span><br><span class="line">fn.apply(obj); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure>
<p>用 call 和 apply 函数的时候还要注意，如果传递给 this 的值不是一个对象，JavaScript 会尝试使用内部 ToObject 操作将其转换为对象。</p>
<p>6.调用obj.bind()时可以创建一个与obj有着相同函数体和作用域的函数，但this将永久地被绑定到了bind的第一个参数，无论这个函数是如何被调用的。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;<span class="attr">name</span>: <span class="string">'Jake'</span>&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sayName</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">this</span>.name)</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> fn = sayName.bind(obj);  </span><br><span class="line"><span class="comment">// 注意 这里 fn还是一个函数</span></span><br><span class="line"><span class="comment">//功能和 sayName 一模一样，区别只在于它里面的 this 是 obj</span></span><br><span class="line">fn() <span class="comment">// 输出： 'Jake'</span></span><br></pre></td></tr></table></figure>
<p>7.原型链中的 this仍然指向调用它的对象，这很好理解：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> fn = &#123;</span><br><span class="line">  add : <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.a + <span class="keyword">this</span>.b; </span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> p = <span class="built_in">Object</span>.create(fn);</span><br><span class="line">p.a = <span class="number">1</span>;</span><br><span class="line">p.b = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(p.add()); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure>
<p>8.当函数被用作事件处理函数时，它的this指向触发事件的元素:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 被调用时，将关联的元素变成蓝色</span></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">bluify</span>(<span class="params">e</span>)</span>&#123;</span><br><span class="line">    <span class="comment">//在控制台打印出所点击元素</span></span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">this</span>);</span><br><span class="line">    <span class="comment">//阻止时间冒泡</span></span><br><span class="line">    e.stopPropagation();</span><br><span class="line">    <span class="comment">//阻止元素的默认事件</span></span><br><span class="line">    e.preventDefault();      </span><br><span class="line">    <span class="keyword">this</span>.style.backgroundColor = <span class="string">'#A5D9F3'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 获取文档中的所有元素的列表</span></span><br><span class="line">  <span class="keyword">var</span> elements = <span class="built_in">document</span>.getElementsByTagName(<span class="string">'*'</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 将bluify作为元素的点击监听函数，当元素被点击时，就会变成蓝色</span></span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span> ; i&lt;elements.length ; i++)&#123;</span><br><span class="line">    elements[i].addEventListener(<span class="string">'click'</span>, bluify, <span class="literal">false</span>);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>
<h4 id="箭头函数"><a href="#箭头函数" class="headerlink" title="箭头函数"></a>箭头函数</h4><p>由于this的高复杂性，this绑定也成为了JavaScript中最常出错的因素之一。因此在ES6中的箭头函数没有了this绑定，必须通过查找作用域链来决定其值，而在全局代码中，它将被设置为全局对象：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = <span class="keyword">this</span>;</span><br><span class="line"><span class="keyword">var</span> foo = <span class="function">(<span class="params">(</span>) =&gt;</span> <span class="keyword">this</span>);</span><br><span class="line"><span class="built_in">console</span>.log(foo() === obj); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
<p>由于箭头函数不绑定this，所以 call() / apply() / bind() 方法对于箭头函数来说只是传入参数，对它的 this毫无影响。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//接着上面的代码</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = &#123;<span class="attr">foo</span>: foo&#125;;</span><br><span class="line"><span class="built_in">console</span>.log(a.foo() === obj); <span class="comment">// true</span></span><br><span class="line"><span class="comment">// 用call来绑定this</span></span><br><span class="line"><span class="built_in">console</span>.log(foo.call(a) === obj); <span class="comment">// true</span></span><br><span class="line"><span class="comment">// 用call来绑定this</span></span><br><span class="line">foo = foo.bind(a);</span><br><span class="line"><span class="built_in">console</span>.log(foo() === obj); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
<p>考虑到 this 是词法层面上的，严格模式中与 this 相关的规则都将被忽略。（可以忽略是否在严格模式下的影响）</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="function"><span class="params">()</span> =&gt;</span> &#123;<span class="string">'use strict'</span>; <span class="keyword">return</span> <span class="keyword">this</span>&#125;;</span><br><span class="line"><span class="keyword">var</span> b = <span class="function"><span class="params">()</span> =&gt;</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>&#125;;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>,a() === <span class="built_in">window</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">2</span>,a() === b());</span><br><span class="line"><span class="comment">//1 true</span></span><br><span class="line"><span class="comment">//2 true</span></span><br></pre></td></tr></table></figure>
<p>而当箭头函数作为方法调用时，this会是怎样的呢？</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  a: <span class="number">1</span>,</span><br><span class="line">  b: <span class="function"><span class="params">()</span> =&gt;</span> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a, <span class="keyword">this</span>),</span><br><span class="line">  c: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log( <span class="keyword">this</span>.a, <span class="keyword">this</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.b();  <span class="comment">// undefined Window&#123;postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window,…&#125;</span></span><br><span class="line">obj.c();  <span class="comment">// 1 &#123;a: 1, b: ƒ, c: ƒ&#125;</span></span><br></pre></td></tr></table></figure>
<p>可以看到的是作为方法的箭头函数this指向全局对象(window)，而普通函数中的this则指向调用它的对象。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/我所知道的JavaScript作用域/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/我所知道的JavaScript作用域/" itemprop="url">我所知道的JavaScript作用域</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/JavaScript/" itemprop="url" rel="index">
                    <span itemprop="name">JavaScript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  1,007
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  4
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>说到作用域我们首先要讨论一下执行环境。执行环境（ExecutionContext）定义了变量或函数有权访问的其他数据，决定了它们的行为。 与之相关的<strong>变量对象</strong>（Variable Object），环境中定义的所有变量和函数都保存在这个对象中，虽然我们无法访问这个对象，但解析器在处理数据时会在后台使用它。</p>
<p>当代码在一个环境中执行时，会创建变量对象的一个作用域链。作用域链可以保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端，始终都是大年执行的代码所在环境的变量，如果执行环境是函数的话，那么它的活动对象就是<strong>变量对象</strong>，活动对象（activeObject）在最开始只包含一个对象-arguments对象（该对象在全局环境中不存在的 ），而下一个变量对象来自外部环境，再下一个变量对象来自下一个外部环境，这样一直延续到全局执行环境，全局执行环境的变量对象始终都只是作用域链中的最后一个对象。</p>
<p>JavaScript的作用域接近于词法作用域（因为有eval和with，可以欺骗作用域），词法作用域就是定义在词法阶段的作用域，换句话来说，词法作用域是由你写代码将变量和块作用域写在哪里决定的。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> global = <span class="number">1</span>;</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">param1</span>)</span>&#123;</span><br><span class="line">     <span class="keyword">var</span> local1 = <span class="string">'local1'</span>;</span><br><span class="line">     <span class="keyword">var</span> local2 = <span class="string">'local2'</span>;</span><br><span class="line">     <span class="function"><span class="keyword">function</span> <span class="title">fn1</span>(<span class="params">param2</span>)</span>&#123;</span><br><span class="line">         <span class="keyword">var</span> local2 = <span class="string">'hello local2'</span>;</span><br><span class="line">         <span class="built_in">console</span>.log(local1); <span class="comment">//local1</span></span><br><span class="line">         <span class="built_in">console</span>.log(local2); <span class="comment">//hello local2</span></span><br><span class="line">     &#125;;</span><br><span class="line"></span><br><span class="line">     <span class="function"><span class="keyword">function</span> <span class="title">fn2</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">         <span class="keyword">var</span> local2 = <span class="string">'my local2'</span>;</span><br><span class="line">         fn2(local2); <span class="comment">//my local2</span></span><br><span class="line">     &#125;;</span><br><span class="line">     <span class="built_in">console</span>.log(local2); <span class="comment">//local2</span></span><br><span class="line">     <span class="built_in">console</span>.log(global); <span class="comment">//1</span></span><br><span class="line"> &#125;;</span><br></pre></td></tr></table></figure>
<p>当浏览器拿到一段代码的时候不会直接去执行它。而是会将这段代码抽象称语法树，在确定没有问题的情况下再去执行它。具体为以下两个步骤：</p>
<ol>
<li>javascript预编译：就是通过语法分析和预解析构造合法的语法分析树，读取变量和函数的声明，并确定其作用域即生效范围。</li>
<li>javascript执行：执行具体的代码，JavaScript引擎在执行每个函数实例时，都会创建一个执行环境和活动对象（它们属于宿主对象，与函数实例的生命周期保持一致）</li>
</ol>
<p>而通过上面一段代码的执行，我们不难发现关于作用域的这几个规则：</p>
<ol>
<li>在对变量进行查找时，是由作用域的最内部开始逐级而上进行查询，这个过程会一直到找到标识符为止，如果找不到标识符，通常会导致错误发生。</li>
<li>内部作用域的变量对外层的作用域有遮蔽效应,即函数内部的同名变量或参数其优先级高于全局同名变量。（如local2)</li>
<li>局部变量的作用域仅在于函数内部，外部不能访问函数内部的变量。</li>
</ol>
<p><em>要注意的是某个执行环境中的所有代码都执行完毕的时候，该环境会被销毁，保存其中的所有变量和函数也会被销毁（全局执行环境会在应用程序关闭后再销毁）</em></p>
<h6 id="块级作用域"><a href="#块级作用域" class="headerlink" title="块级作用域"></a>块级作用域</h6><p>在早期的ECMAScript中，并没有块级作用域，但ES6开始引进了块级作用域，关于块级作用域的最主要的用法便是下文的let和const。具体表现如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> a = <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">const</span> b = <span class="number">3</span>; </span><br><span class="line">    a = <span class="number">3</span>; </span><br><span class="line">    b = <span class="number">4</span>;<span class="comment">// TypeError: Assignment to constant variable</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">console</span>.log( a ); <span class="comment">// ReferenceError: a is not defined</span></span><br><span class="line"><span class="built_in">console</span>.log( b ); <span class="comment">// ReferenceError: b is not defined</span></span><br></pre></td></tr></table></figure>
<p>如上所示，在块级作用域内使用let和const声明的变量在块级作用域外不能被访问，且const定义的变量是常量，不能被改变。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/简述JavaScript模块化（一）/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/简述JavaScript模块化（一）/" itemprop="url">简述JavaScript模块化历程（一）</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/JavaScript/" itemprop="url" rel="index">
                    <span itemprop="name">JavaScript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  2,724
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  10
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>在早期编写JavaScript时，我们只需在 script 标签内写入JavaScript的代码就可以满足我们对页面交互的需要了。但随着时间的推移，时代的发展，原本的那种简单粗暴的编写方式所带来的诸如逻辑混乱，页面复杂，可维护性差，全局变量暴露等问题接踵而至，前辈们为了解决这些问题提出了很种的解决方案，其中之一就是JavaScript模块化编程。总的来说，它有以下四种优点：</p>
<ol>
<li>解决项目中的全局变量污染的问题。 </li>
<li>开发效率高，有利于多人协同开发。 </li>
<li>职责单一，方便代码复用和维护 。 </li>
<li>解决文件依赖问题，无需关注引用文件的顺序。</li>
</ol>
<h4 id="先行者CommonJs"><a href="#先行者CommonJs" class="headerlink" title="先行者CommonJs"></a>先行者CommonJs</h4><p>2009年Node.js横空出世，将JavaScript带到了服务器端领域。而对于服务器端来说，没有模块化那可是不行的。因此CommonJs社区的大牛们开始发力了，制定了一个与社区同名的关于模块化的规范——CommonJs。它的规范主要如下：</p>
<ol>
<li>模块的标识应遵循的规则（书写规范）。</li>
<li>定义全局函数require，通过传入模块标识来引入其他模块，执行的结果即为别的模块暴露出来的API。</li>
<li>如果被require函数引入的模块中也包含依赖，那么依次加载这些依赖。</li>
<li>如果引入模块失败，那么require函数应该报一个异常。</li>
<li>模块通过变量exports来向外暴露API，exports只能是一个对象，暴露的API须作为此对象的属性。</li>
</ol>
<p>根据CommonJS规范的规定，每个文件就是一个模块，有自己的作用域，也就是在一个文件里面定义的变量、函数、类，都是私有的，对其他文件是不可见的。通俗来讲，就是说在模块内定义的变量和函数是无法被其他的模块所读取的，除非定义为全局对象的属性。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// addA.js</span></span><br><span class="line"><span class="keyword">const</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">const</span> addA = <span class="function"><span class="keyword">function</span>(<span class="params">value</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> value + a;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>上面代码中，变量a和函数addA，是当前文件addA.js私有的，其他文件不可见。如果想在多个文件中分享变量a，必须定义为global对象的属性：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">global.a = <span class="number">1</span>;</span><br></pre></td></tr></table></figure>
<p>这样我们就能在其他的文件中访问变量a了，但这种写法不可取，输出模块对象最好的方式是module.exports：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// addA.js</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">var</span> addA = <span class="function"><span class="keyword">function</span>(<span class="params">value</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> value + x;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports.addA = addA;</span><br></pre></td></tr></table></figure>
<p>上面代码通过module.exports对象输出了一个函数，该函数就是模块外部与内部通信的桥梁。加载模块需要使用require方法，该方法读取一个文件并执行，最后返回文件内部的module.exports对象。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> example = <span class="built_in">require</span>(<span class="string">'./addA.js'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(example.addA(<span class="number">1</span>));  <span class="comment">//2</span></span><br></pre></td></tr></table></figure>
<p>CommonJs看起来是一个很不错的选择，拥有模块化所需要的严格的入口和出口，看起来一切都很美好，但它的一个特性却决定了它只能在服务器端大规模使用，而在浏览器端发挥不了太大的作用，那就是同步！这在服务器端不是什么问题，但放在浏览器端就出现问题了，因为文件都放在服务器上，如果网速不够快的话，前面的文件如果没有加载完成，浏览器就会失去响应！因此为了在浏览器上也实现模块化得来个异步的模块化才行！根据这个需求，我们的下一位主角——AMD就产生了！</p>
<h4 id="AMD-异步模块定义"><a href="#AMD-异步模块定义" class="headerlink" title="AMD 异步模块定义"></a>AMD 异步模块定义</h4><p>AMD的全名叫做：Asynchronous Module Definition即异步模块定义。它采用了异步的方式来加载模块，然后在回调函数中执行主逻辑，因此模块的加载不影响它后面的模块的运行。它的规范如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">define(id?, dependencies?, factory);</span><br></pre></td></tr></table></figure>
<ol>
<li>用全局函数define来定义模块;</li>
<li>id为模块标识，遵从CommonJS Module Identifiers规范</li>
<li>dependencies为依赖的模块数组，在factory中需传入形参与之一一对应</li>
<li>如果dependencies的值中有”require”、”exports”或”module”，则与commonjs中的实现保持一致</li>
<li>如果dependencies省略不写，则默认为[“require”, “exports”, “module”]，factory中也会默认传入require,exports,module</li>
<li>如果factory为函数，模块对外暴漏API的方法有三种：return任意类型的数据、exports.xxx=xxx、module.exports=xxx</li>
<li>如果factory为对象，则该对象即为模块的返回值</li>
</ol>
<p>具体分析AMD我们通过require.js来进行。require.js是一个非常小巧的JavaScript模块载入框架，是AMD规范最好的实现者之一，require.js的出现主要是来解决两个问题：</p>
<ol>
<li>实现JavaScript文件的异步加载，避免网页失去响应。</li>
<li>管理模块的依赖性，管理模块的相互独立性，也就是我们常说的低耦合，这有利于代码的编写与维护。</li>
</ol>
<p>使用require.js我们首先要加载它，为了避免浏览器未响应，我们在后面可以加上async,告诉浏览器这个文件需要异步加载（IE不支持该属性，所以需要把defer也加上）：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;script src=<span class="string">"js/require.js"</span> defer <span class="keyword">async</span>=<span class="string">"true"</span> &gt;<span class="xml"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure>
<p>定义模块时，在require.js中我们可以使用define，但define对于需要定义的模块是否是独立的模块的写法是不同;所谓的独立模块就是指不依赖于其他模块的模块，而非独立模块就是指不依赖于其他模块的模块。</p>
<p>define在定义独立模块时有两种写法，一种是直接定义对象；另一种是定义一个函数，在函数内的返回值就是输出的模块了：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">define(&#123;</span><br><span class="line">    method1: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;&#125;,</span><br><span class="line">    method2: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;&#125;,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//等价于</span></span><br><span class="line">define(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> &#123;</span><br><span class="line">	    method1: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;&#125;,</span><br><span class="line">		method2: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;&#125;,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p>
<p>如果define定义非独立模块，那么它的语法就规定一定是这样的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">define([<span class="string">'module1'</span>, <span class="string">'module2'</span>], <span class="function"><span class="keyword">function</span>(<span class="params">m1, m2</span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        method: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">            m1.methodA();</span><br><span class="line">			m2.methodB();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>define在这个时候接受两个参数，第一个参数是module是一个数组，它的成员是我们当前定义的模块所依赖的模块，只有顺利加载了这些模块，我们新定义的模块才能成功运行。第二个参数是一个函数，当前面数组内的成员全部加载完之后它才运行，它的参数m与前面的module是一一对应的。这个函数必须返回一个对象，以供其他模块调用，需要注意的是，回调函数必须返回一个对象，这个对象就是你定义的模块。     </p>
<p>在加载模块方面，AMD和CommonJs都是使用require。require.js也同样如此，它要求两个参数：module，callback：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>([<span class="built_in">module</span>], callback);</span><br></pre></td></tr></table></figure>
<p>第一个参数[module]，是一个数组，里面的成员就是需要加载的模块；第二个参数callback，则是加载成功之后的回调函数。<br>require方法本身也是一个对象，它带有一个config方法，用来配置require.js运行参数。config方法接受一个对象作为参数。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//别名配置</span></span><br><span class="line">requirejs.config(&#123;</span><br><span class="line">    paths: &#123;</span><br><span class="line">        jquery: [   <span class="comment">//如果第一个路径不能完成加载，就调到第二个路径继续进行加载</span></span><br><span class="line">            <span class="string">'//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js'</span>,</span><br><span class="line">            <span class="string">'lib/jquery'</span>   <span class="comment">//本地文件中不需要写.js</span></span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">//引入模块，用变量$表示jquery模块</span></span><br><span class="line">requirejs([<span class="string">'jquery'</span>], <span class="function"><span class="keyword">function</span> (<span class="params">$</span>) </span>&#123;</span><br><span class="line">    $(<span class="string">'body'</span>).css(<span class="string">'background-color'</span>,<span class="string">'black'</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>虽然require.js实现了异步的模块化，但它仍然有一些不足的地方，在使用require.js的时候，我们必须要提前加载所有的依赖，然后才可以使用，而不是需要使用时再加载，使得初次加载其他模块的速度较慢，提高了开发成本。</p>
<h4 id="CMD-通用模块定义"><a href="#CMD-通用模块定义" class="headerlink" title="CMD 通用模块定义"></a>CMD 通用模块定义</h4><p>CMD的全称是Common Module Definition，即通用模块定义。它是由蚂蚁金服的前端大佬——玉伯提出来的，实现的JavaScript库为sea.js。它和AMD的require.js很像，但加载方式不同，它是按需就近加载的，而不是在模块的开始全部加载完成。它有以下两大核心特点：</p>
<ol>
<li>简单友好的模块定义规范：Sea.js 遵循 CMD 规范，可以像 Node.js 一般书写模块代码。</li>
<li>自然直观的代码组织方式：依赖的自动加载、配置的简洁清晰，可以让我们更多地享受编码的乐趣。</li>
</ol>
<p>在CMD规范中，一个文件就是一个模块，代码书写的格式是这样的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">define(factory);</span><br></pre></td></tr></table></figure>
<p>当factory为函数时，表示模块的构造方法，执行该方法，可以得到该模块对外提供的factory接口，factory 方法在执行时，默认会传入三个参数：require、exports 和 module：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 所有模块都通过 define 来定义</span></span><br><span class="line">define(<span class="function"><span class="keyword">function</span>(<span class="params">require, exports, module</span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 通过 require 引入依赖</span></span><br><span class="line">  <span class="keyword">var</span> $ = <span class="built_in">require</span>(<span class="string">'jquery'</span>);</span><br><span class="line">  <span class="keyword">var</span> Spinning = <span class="built_in">require</span>(<span class="string">'./spinning'</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 通过 exports 对外提供接口</span></span><br><span class="line">  exports.doSomething = ...</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 或者通过 module.exports 提供整个接口</span></span><br><span class="line">  <span class="built_in">module</span>.exports = ...</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>它与AMD的具体区别其实我们也可以通过代码来表现出来，AMD需要在模块开始前就将依赖的模块加载出来，即依赖前置；而CMD则对模块按需加载，即依赖就近，只有在需要依赖该模块的时候再require就行了：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AMD规范</span></span><br><span class="line">define([<span class="string">'./a'</span>, <span class="string">'./b'</span>], <span class="function"><span class="keyword">function</span>(<span class="params">a, b</span>) </span>&#123;  <span class="comment">// 依赖必须一开始就写好  </span></span><br><span class="line">   a.doSomething()    </span><br><span class="line">   <span class="comment">// 此处略去 100 行    </span></span><br><span class="line">   b.doSomething()    </span><br><span class="line">   ...</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// CMD规范</span></span><br><span class="line">define(<span class="function"><span class="keyword">function</span>(<span class="params">require, exports, module</span>) </span>&#123;</span><br><span class="line">   <span class="keyword">var</span> a = <span class="built_in">require</span>(<span class="string">'./a'</span>)   </span><br><span class="line">   a.doSomething()   </span><br><span class="line">   <span class="comment">// 此处略去 100 行   </span></span><br><span class="line">   <span class="keyword">var</span> b = <span class="built_in">require</span>(<span class="string">'./b'</span>) </span><br><span class="line">   <span class="comment">// 依赖可以就近书写   </span></span><br><span class="line">   b.doSomething()</span><br><span class="line">   <span class="comment">// ... </span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>需要注意的是Sea.js的执行模块顺序也是严格按照模块在代码中出现(require)的顺序。</p>
<p>从运行速度的角度来讲，AMD虽然在第一次使用时较慢，但在后面再访问时速度会很快；而CMD第一次加载会相对快点，但后面的加载都是重新加载新的模块，所以速度会慢点。总的来说,<br>require.js的做法是并行加载所有依赖的模块, 等完成解析后, 再开始执行其他代码, 因此执行结果只会”停顿”1次, 而Sea.js在完成整个过程时则是每次需要相应模块都需要进行加载，这期间会停顿是多次的，因此require.js从整体而言相对会比Sea.js要快一些。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/简述JavaScript模块化（二）/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/简述JavaScript模块化（二）/" itemprop="url">简述JavaScript模块化历程（二）</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/JavaScript/" itemprop="url" rel="index">
                    <span itemprop="name">JavaScript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  2,904
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  11
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>前置阅读：<a href="https://www.jianshu.com/p/3fbaa3ae70e6/" target="_blank" rel="noopener">简述JavaScript模块化（一）</a></p>
<p>在前面一文中，我们对前端模块化所经历的三个阶段进行了了解：</p>
<ol>
<li>CommonJs,由于是同步的，所有主要应用于服务器端，以Node.js为代表。</li>
<li>AMD，异步模块定义，预加载，推荐依赖前置。以require.js为代表。</li>
<li>CMD，通用模块加载，懒加载，推荐依赖就近。以Sea.js为代表。</li>
</ol>
<p>而在ES6已经大行其道的今天，ES6中所提供的模块化的方法也自然而然成了我们进行JavaScript模块化编程的标准，因此ES6模块的语法虽然在一些较为老的浏览器上不能直出，需要进行转译，但却代表着未来的JavaScript发展趋势。</p>
<h4 id="ES6模块特性"><a href="#ES6模块特性" class="headerlink" title="ES6模块特性"></a>ES6模块特性</h4><p>在ES6中将模块认为是自动运行在严格模式下并且没有办法退出运行的JavaScript代码。在一个模块中定义的变量不会自动被添加到全局共享的作用域之中，这个变量只能作用在这个作用域中。此外模块还必须导出一些外部文件可以访问的元素，以供其他模块或代码使用。</p>
<p>除了这个基本特性，ES6模块还有两大特性也十分重要，需要额外注意：</p>
<ul>
<li>首先是在模块的顶部this值是undefined，这是由于在ES6中的模块的代码是在严格模式下执行的。（如果对this不是很熟悉的可以去看我的这篇文章<a href="https://www.jianshu.com/p/8d6cc7ad9c58/" target="_blank" rel="noopener">：深入浅出this关键字</a>）</li>
<li>其次，模块不支持HTML风格的代码注释，这是早期浏览器所遗留下的JavaScript特性，在ES6的语法里不予支持。</li>
</ul>
<h4 id="基本用法-模块加载"><a href="#基本用法-模块加载" class="headerlink" title="基本用法-模块加载"></a>基本用法-模块加载</h4><p>首先我们来看浏览器是如何加载模块的。其实在ES6规范出来之前，web浏览器就规定了三种方式来引入JavaScript文件：</p>
<ul>
<li>在没有src属性的script元素中直接内嵌JavaScript代码</li>
<li>在 script 元素中通过src属性指定一个地址来加载JavaScript代码文件</li>
<li>通过Web Worker或Service Worker的方法加载并执行JavaScript代码</li>
</ul>
<p>而在浏览器中，默认的行为就是将JavaScript作为脚本来进行加载，而非模块。所以我们要告诉浏览器我们加载的是模块，方法就是在 script 元素中，将type属性指定为”module”。具体看下面的示例：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一种方式</span></span><br><span class="line">&lt;script type=<span class="string">""</span><span class="built_in">module</span>&gt;</span><br><span class="line">    <span class="keyword">import</span> &#123; add &#125; <span class="keyword">from</span> <span class="string">"./example"</span>;</span><br><span class="line">    <span class="keyword">let</span> num = add(<span class="number">1</span>, <span class="number">1</span>);</span><br><span class="line">&lt;<span class="regexp">/script&gt;</span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/  第二种方式</span></span><br><span class="line"><span class="regexp">&lt;script type="module" src="example.js"&gt;</span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/ 第三种方式，以脚本的方式加载example.js</span></span><br><span class="line"><span class="regexp">let worker = new Worker("example.js");</span></span><br></pre></td></tr></table></figure>
<p>当HTML解析器遇到script元素的type=”module”的时候，模块文件就开始下载，直到文件被完全解析完成才会去执行模块内的代码。模块文件是按照他们出现在HTML文件中顺序执行的.</p>
<h4 id="基本用法-导出"><a href="#基本用法-导出" class="headerlink" title="基本用法-导出"></a>基本用法-导出</h4><p>在ES6中我们可以使用export关键字将一部分代码暴露给其他模块，以供其他模块或代码使用。先让我们来看看export关键字在MDN的定义吧：</p>
<blockquote>
<p>export语句用于在创建JavaScript模块时，从模块中导出函数、对象或原始值，以便其他程序可以通过 import 语句使用它们。（<br>此特性目前仅在 Safari 和 Chrome 原生实现。它在许多转换器中实现，如Traceur Compiler，Babel或Rollup。）</p>
</blockquote>
<p>通过MDN的定义我们可以知道：export关键字可以将其放在任何函数、对象或原始值前面，从而将它们从模块中导出。示例如下：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//   ./example.js</span></span><br><span class="line"><span class="comment">// 导出变量</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="comment">// 导出函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">addA</span>(<span class="params">value</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> value + a;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//导出类</span></span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">add1</span> </span>&#123;</span><br><span class="line">    <span class="keyword">constructor</span>(value) &#123;</span><br><span class="line">        <span class="keyword">this</span>.value = value + a;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//这个函数就是这个模块所私有的，在外部不能访问它</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">say1</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'我是不是很帅'</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//这又是个函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">say2</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'没错我就是很帅'</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//在后面对函数进行导出,它就不是私有的了</span></span><br><span class="line"><span class="keyword">export</span> say2;</span><br></pre></td></tr></table></figure></p>
<p>需要注意的是：使用export导出的函数和类都需要一个名称，除非使用default关键字，否则就不能用这个方法导出匿名函数或类。所以当我们需要导出匿名的函数或者类时，我们可以这么做：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//   ./example.js</span></span><br><span class="line"><span class="comment">//导出匿名函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b；</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//或者导出匿名的类</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> </span>&#123;</span><br><span class="line">consturctor(value) &#123;</span><br><span class="line">    <span class="keyword">this</span>.value = value + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>具体关于default关键字的用法我会在后面做具体介绍，现在只需记住：当我们需要导出匿名的函数或者类时要使用export default语法。</p>
<h4 id="基本语法-导入"><a href="#基本语法-导入" class="headerlink" title="基本语法-导入"></a>基本语法-导入</h4><p>在ES6中，从模块中导入的功能可以通过import关键字。import语句由两部分组成：要导入元素的标识符和元素应当从哪个模块导入。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  ./say.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; say2 &#125; <span class="keyword">from</span> <span class="string">"./example.js"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(say2()); <span class="comment">// '没错我就是很帅'</span></span><br></pre></td></tr></table></figure>
<p>import 后面的大括号中的say2表示从规定模块导入的元素的名称。关键字from后面的字符串则表示要导入的模块的路径，这通常是包含模块的.js文件的相对或绝对路径名，需要注意的是只允许使用单引号和双引号的字符串来包裹路径，浏览器使用的路径格式与传给 script 元素的相同，所以必须把文件的扩展名也加上。</p>
<blockquote>
<p>（注：由于Node.js遵循基于文件系统前缀以区分本地文件个包的惯例，即example是一个包，而./exampple.js是一个本地文件。为了更好的兼容多个浏览器Node.js环境，我们一定要在路径前包含./或../来表示要导入的文件。）</p>
</blockquote>
<p>除此之外，我们还可以导入多个元素或者直接导入整个模块：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 导入多个元素</span></span><br><span class="line">improt &#123; a, addA, say2 &#125; <span class="keyword">from</span> <span class="string">"./example.js"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(a); <span class="comment">// 1</span></span><br><span class="line">sonsole.log(addA(<span class="number">1</span>); <span class="comment">// 2</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 导入整个模块</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> example <span class="keyword">from</span> <span class="string">"./example.js"</span></span><br><span class="line"><span class="built_in">console</span>.log(example.a); <span class="comment">// 1</span></span><br><span class="line">sonsole.log(example.addA(<span class="number">1</span>); <span class="comment">// 2</span></span><br><span class="line"><span class="built_in">console</span>.log(example.say2()); <span class="comment">// '没错我就是很帅'</span></span><br></pre></td></tr></table></figure>
<p>上面的导入整个模块就是把example.js中导出的所有元素全部加载到一个叫做example的对象中，而所导出的元素就会作为example的属性被访问。因为example对象是作为example.js中所导出成员的命名空间对象而被创建的，所以这种导入方式被称为命名空间导入（name space import)。</p>
<p>还有一点要注意的是，不管import语句把一个模块写了多少次，该模块只执行一次。意思就是，在首次执行导入模块后，实例化的模块就会被保存在内存中，只要使用import语句引用它就可以重复使用它：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 首次导入需要加载模块example.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; a &#125; <span class="keyword">from</span> <span class="string">"./example.js"</span></span><br><span class="line"><span class="comment">// 下面的两个import将无需加载example.js了</span></span><br><span class="line"><span class="keyword">import</span> &#123; addA &#125; <span class="keyword">from</span> <span class="string">"./example.js"</span></span><br><span class="line"><span class="keyword">import</span> &#123; say2 &#125; <span class="keyword">from</span> <span class="string">"./example.js"</span></span><br></pre></td></tr></table></figure>
<p>当从模块中导入一个元素时，它与const是一样无法定义另一个同名变量和导入一个同名元素，也无法在import语句前使用元素或者改变导出的元素的值：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//接上面的代码</span></span><br><span class="line"></span><br><span class="line">say2 = <span class="number">1</span> ;  <span class="comment">//会抛出一个错误</span></span><br></pre></td></tr></table></figure>
<p>这是由于ES6的import语句为导入的元素创建的是只读绑定的标识符，而不是原始绑定。因此元素只有在被导出的模块中才可以被修改，即使是将该模块的全部导入也无法修改其中的元素。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//   ./example.js</span></span><br><span class="line"><span class="comment">// 这是一个函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">setA</span>(<span class="params">newA</span>) </span>&#123;</span><br><span class="line">    a = newA;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//  ./say.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; a, setA &#125; <span class="keyword">from</span> <span class="string">"./example"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(a);  <span class="comment">// 1</span></span><br><span class="line">a = <span class="number">2</span>;   <span class="comment">//抛出错误</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 所以我们得这么做</span></span><br><span class="line">setA(<span class="number">2</span>); </span><br><span class="line"><span class="built_in">console</span>.log(a);  <span class="comment">// 2</span></span><br></pre></td></tr></table></figure>
<p>调用setA(2)时会返回到example.js中去执行，将a设置为2。由于say.js导入的只是a的只读绑定的标识符而已，因此会自动进行更改。</p>
<h4 id="其他基本语法"><a href="#其他基本语法" class="headerlink" title="其他基本语法"></a>其他基本语法</h4><h5 id="1-语法限制"><a href="#1-语法限制" class="headerlink" title="1.语法限制"></a>1.语法限制</h5><p>export和import在语法上还有一个重要的限制，那就是他们必须在条件语句和函数之外使用，例如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (ture) &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">var</span> a = <span class="number">1</span>;      <span class="comment">//语法错误</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">imp</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">import</span> a <span class="keyword">from</span> <span class="string">"./example.js"</span>; <span class="comment">//语法错误</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>由于模块语法存在的其中一个原因是让JavaScript引擎可以静态地确定哪些代码是可以导出的，因此export和import语句被设计成静态的，不能进行任何形式的动态导出或导入。</p>
<h5 id="2-重命名解决"><a href="#2-重命名解决" class="headerlink" title="2.重命名解决"></a>2.重命名解决</h5><p>有时在开发中，我们在导入一些元素后不想使用它们的原始名称了，我们就可以在导出过程或者导入过程中去改变导出元素的名称：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 导出过程</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> &#123; add <span class="keyword">as</span> add1 &#125;;  <span class="comment">//在导入过程中必须使用add1作为名称 </span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 导入过程</span></span><br><span class="line"><span class="keyword">import</span> &#123;add <span class="keyword">as</span> add1 &#125; <span class="keyword">from</span> <span class="string">"./example"</span></span><br><span class="line"><span class="built_in">console</span>.log(add1(<span class="number">1</span>,<span class="number">1</span>));  <span class="comment">// 2</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> add); <span class="comment">//undefined</span></span><br></pre></td></tr></table></figure>
<h5 id="模块的默认值"><a href="#模块的默认值" class="headerlink" title="模块的默认值"></a>模块的默认值</h5><p>在CommonJS等其他的模块化规范中，从模块中导出或导入默认值是一个常见的用法，因此在ES6中也延用了这种用法并进行了优化。在ES6中我们可以使用default关键字来指定默认值，并且一个模块只能默认一个导出值：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./example.js</span></span><br><span class="line"><span class="comment">// 第一种默认导出语法</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 第二种默认导出语法</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> add;</span><br><span class="line"><span class="comment">// 第三种默认导出语法</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> &#123; add <span class="keyword">as</span> <span class="keyword">default</span> &#125;;</span><br></pre></td></tr></table></figure>
<p>需要注意的是第三种语法，default关键字虽然不能作为元素的名称，但可以作为元素的属性名称，因此可以使用as语法将add函数的属性设置为default。</p>
<p>导入默认值的语法则是这样的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  第一种语法</span></span><br><span class="line"><span class="keyword">import</span> add <span class="keyword">from</span> <span class="string">"./example"</span>;</span><br><span class="line"><span class="comment">//  第二种语法</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="keyword">default</span> <span class="keyword">as</span> add &#125; <span class="keyword">from</span> <span class="string">"./example"</span>;</span><br></pre></td></tr></table></figure>
<p>看到这里有些朋友可能会发现，我们的第一种语法中import关键字后面并没有加大括号，认为这是错误的。其实这是导入默认值的独特语法，在这的本地名称add用于表示模块导出的任何默认函数，这种语法是最纯净的，ES6标准创建团队的大佬们也希望这种语法能成为web主流的模块导入形式。</p>
<p>我们前面说的导入匿名函数也同样使用这种语法：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./example.js</span></span><br><span class="line"><span class="comment">//导出匿名函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b；</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ./say.js</span></span><br><span class="line"><span class="keyword">import</span> add <span class="keyword">from</span> <span class="string">"./example"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(add(<span class="number">1</span>,<span class="number">1</span>));  <span class="comment">// 2</span></span><br></pre></td></tr></table></figure></p>
<p>在这里本地名称add就是用于表示上面的匿名函数的。</p>
<h5 id="导出已导入的元素"><a href="#导出已导入的元素" class="headerlink" title="导出已导入的元素"></a>导出已导入的元素</h5><p>我们同样可以在本模块内导出我们在本模块内导入的元素，有以下几种语法：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  第一种语法</span></span><br><span class="line"><span class="keyword">import</span> &#123; add &#125; <span class="keyword">from</span> ./example.js;</span><br><span class="line"><span class="keyword">export</span> &#123; add &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  第二种语法</span></span><br><span class="line"><span class="keyword">export</span> &#123; add &#125; <span class="keyword">from</span> ./example.js;</span><br><span class="line"></span><br><span class="line"><span class="comment">//换一个名称导出</span></span><br><span class="line"><span class="keyword">export</span> &#123; add <span class="keyword">as</span> add1 &#125; <span class="keyword">from</span> ./example.js; <span class="comment">//以add这个名称导入，再以add1的名称导出</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 导出整个模块</span></span><br><span class="line"><span class="keyword">export</span> *  <span class="keyword">from</span> ./example.js;</span><br></pre></td></tr></table></figure>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://srtian96.gitee.io/blog/blog/2018/05/06/变量提升和函数声明提升/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Srtian">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Srtian'Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/05/06/变量提升和函数声明提升/" itemprop="url">变量提升和函数声明提升</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-05-06T12:22:54+08:00">
                2018-05-06
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/JavaScript/" itemprop="url" rel="index">
                    <span itemprop="name">JavaScript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  1,394
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  5
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h3 id="JavaScript的执行步骤"><a href="#JavaScript的执行步骤" class="headerlink" title="JavaScript的执行步骤"></a>JavaScript的执行步骤</h3><p>要讨论JavaScript中的变量提升和函数声明提升，我们首先要知道的是，当浏览器拿到一段JavaScript代码的时候并不会去直接执行它，浏览器引擎会在执行JavaScript代码之前对其进行<strong>预编译</strong>。总的而言就是下列两个步骤：</p>
<ol>
<li>javascript预编译：就是通过语法分析和预解析构造合法的语法分析树，读取变量和函数的声明，并确定其作用域即生效范围。</li>
<li>javascript执行：执行具体的代码，JavaScript引擎在执行每个函数实例时，都会创建一个执行环境和活动对象（它们属于宿主对象，与函数实例的生命周期保持一致）</li>
</ol>
<h6 id="变量提升"><a href="#变量提升" class="headerlink" title="变量提升"></a>变量提升</h6><p><strong>预编译阶段</strong>的一部分工作就是找到所有的变量和函数声明，并用合适的作用域与之关联起来。因此，包括变量和函数在内的所有声明都会在其他代码执行前首先被处理，例如下面的代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>(a) <span class="comment">//输出undefined;</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">---------------------------------</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">但其实真实的步骤是这样的</span></span><br><span class="line"><span class="comment">会将声明提前到最顶部</span></span><br><span class="line"><span class="comment">而赋值依然会停留在原地</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> a；</span><br><span class="line"><span class="built_in">console</span>.log(a); <span class="comment">//输出undefined</span></span><br><span class="line">a = <span class="number">1</span>;</span><br></pre></td></tr></table></figure>
<p>在预编译阶段会将所有的变量声明和函数声明从它们在原本的位置移动到了最上面。而这个过程就叫做<strong>提升</strong>。而且要注意的是只有声明会被提升，而赋值或其他运行逻辑会留在原地。具体的步骤如下：</p>
<ol>
<li>将var a 提升至作用域的最顶部。</li>
<li>初始化a为undefined。</li>
<li>打印a。</li>
<li>给a赋值为1。</li>
</ol>
<p>而且要注意的是每个作用域都会在各自的作用域内进行提升操作：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(name); <span class="comment">//输出undefined</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">say</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(name); <span class="comment">//输出：undefined</span></span><br><span class="line">  <span class="keyword">var</span> name = <span class="string">'Bob'</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(name); <span class="comment">//输出：'Bob'</span></span><br><span class="line">&#125;;</span><br><span class="line">say();</span><br><span class="line"><span class="keyword">var</span> name = <span class="string">'Jake'</span>;</span><br></pre></td></tr></table></figure>
<p>真实的顺序是这样的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> name；</span><br><span class="line"><span class="built_in">console</span>.log(name); <span class="comment">//输出undefined</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">say</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="keyword">var</span> name；</span><br><span class="line">  <span class="built_in">console</span>.log(name); <span class="comment">//输出：undefined</span></span><br><span class="line">  name = <span class="string">'Bob'</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(name); <span class="comment">//输出：'Bob'</span></span><br><span class="line">&#125;;</span><br><span class="line">say();</span><br><span class="line">name = <span class="string">'Jake'</span>;</span><br></pre></td></tr></table></figure>
<h6 id="函数声明提升"><a href="#函数声明提升" class="headerlink" title="函数声明提升"></a>函数声明提升</h6><p>另外需要注意Javascript中函数的函数声明方式存在的坑。函数声明在提升的时候，实际上会把整个函数提升上去，包括函数定义的部分，所以这么做并不会报错：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fn();</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(a);  <span class="comment">//输出undefined</span></span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p>
<p>而对于函数表达式，与定义其它基本类型变量一样，逐句执行并解析，因此会报错：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">fn();   <span class="comment">//不是ReferenceError,而会是TypeError!</span></span><br><span class="line"><span class="keyword">var</span> fn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="comment">//函数表达式</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p>
<p>另外要记住的是，即使是具名的函数表达式，名称标识符在赋值之前也无法再所在作用域中使用：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fn();  <span class="comment">//TypeError</span></span><br><span class="line">vm();  <span class="comment">//ReferenceError</span></span><br><span class="line"><span class="keyword">var</span> fn = <span class="function"><span class="keyword">function</span> <span class="title">vm</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">//一些有意思的东西</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>上面的代码在进行提升后实际上是这样的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> fn;</span><br><span class="line">fn();  <span class="comment">//TypeError</span></span><br><span class="line">vm();  <span class="comment">//ReferenceError</span></span><br><span class="line">fn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> vm = ...self...</span><br><span class="line">    <span class="comment">//一些有意思的东西</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h6 id="函数优先"><a href="#函数优先" class="headerlink" title="函数优先"></a>函数优先</h6><p>当变量声明和函数声明同时被提升时，函数会首先被提升，然后才是变量（尤其是当有多个重复声明的代码中尤为重要）。我们来看看下面的情况：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">fn();  <span class="comment">//1</span></span><br><span class="line"><span class="keyword">var</span> fn;</span><br><span class="line">fn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>如上所见，当两者同时出现时，函数声明会被提升到最前面，然后再是变量提升（当重复声明时会被忽略）。JavaScript引擎具体的理解如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// var fn;在此，但由于重复声明被忽略</span></span><br><span class="line">fn();  <span class="comment">//1</span></span><br><span class="line">fn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>除此之外，后面出现的函数声明还可以覆盖前面的函数声明：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fn();  <span class="comment">//3</span></span><br><span class="line"><span class="keyword">var</span> fn;</span><br><span class="line">fn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>从上面的代码我们可以看到，在同一个作用域下重复的定义是很糟糕的，经常会导致一些出乎意料的问题，因此我们要避免这样！</p>
<h6 id="块级作用域内部的函数声明"><a href="#块级作用域内部的函数声明" class="headerlink" title="块级作用域内部的函数声明"></a>块级作用域内部的函数声明</h6><p>块级作用域内部的函数声明通常也会被提升到所在作用域的顶部，且这个过程是不被条件判断所控制的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fn();  <span class="comment">// 2</span></span><br><span class="line"><span class="keyword">var</span> a = ture;</span><br><span class="line"><span class="keyword">if</span>(a) &#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        consloe.log(<span class="number">2</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这个过程是不可控的。因此原先的ES5中，规定不能在块级作用域内进行函数声明，只能在全局作用域与函数作用域内声明，但又由于要兼容以往的ES3等，所以在出现上述的情况下，浏览器不会报错。但在ES6中，函数声明是可以在块级作用域中进行声明的，但需要注意以下三点：</p>
<ol>
<li>在严格模式下函数声明会被提前到块级作用域头部。</li>
<li>在非严格模式下，函数声明会被提升至外围函数或全局作用域的顶部。</li>
<li>函数声明只在有大括号的块级作用域才能使用，不然会报错。</li>
</ol>
<p><strong><em>（另外在ES6中的let和const由于暂存死区的原因，不会发生变量提升，这需要注意）</em></strong></p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
  </section>

  
  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/blog/page/5/"><i class="fa fa-angle-left"></i></a><a class="page-number" href="/blog/">1</a><span class="space">&hellip;</span><a class="page-number" href="/blog/page/5/">5</a><span class="page-number current">6</span>
  </nav>



          </div>
          


          

        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      

      <section class="site-overview-wrap sidebar-panel sidebar-panel-active">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image"
                src="/blog/images/avatar.jpg"
                alt="Srtian" />
            
              <p class="site-author-name" itemprop="name">Srtian</p>
              <p class="site-description motion-element" itemprop="description">JUST DO IT.</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/blog/archives/">
              
                  <span class="site-state-item-count">59</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/blog/categories/index.html">
                  <span class="site-state-item-count">15</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/blog/tags/index.html">
                  <span class="site-state-item-count">37</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a href="https://gitee.com/srtian96" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-github"></i>GitHub</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="shenruotian@gmail.com" target="_blank" title="E-Mail">
                      
                        <i class="fa fa-fw fa-envelope"></i>E-Mail</a>
                  </span>
                
            </div>
          

          
          

          
          

          

        </div>
      </section>

      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2019</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Srtian</span>
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js">
</script>
  
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-area-chart"></i>
    </span>
    
      <span class="post-meta-item-text">Site words total count&#58;</span>
    
    <span title="Site words total count">102.8k</span>
  
</div>





        
<div class="busuanzi-count">
  <script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>

  
    <span class="site-uv">
      本站访客数
      <span class="busuanzi-value" id="busuanzi_value_site_uv"></span>
      人次
    </span>
  

  
    <span class="site-pv">
      本站总访问量
      <span class="busuanzi-value" id="busuanzi_value_site_pv"></span>
      次
    </span>
  
</div>








        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/blog/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/blog/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/blog/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/blog/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/blog/js/src/schemes/pisces.js?v=5.1.4"></script>



  

  


  <script type="text/javascript" src="/blog/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/blog/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  
  

  

  

  

</body>
</html>
